概述

如上篇文章所述,count++是非线程安全的,即使类变量count加上volatile修饰,只能保证可见性,无法保证原子性。本文将从源码分析atomic是如何原子操作的。

CAS算法

Atomic类是基于CAS算法实现的,简单一句概括就是,线程修改Atomic类对象的值时,始终用线程私有内存中的旧值和主存中的值比较,只有该两个值一致,才允许将新值设置到主存中去。

源码解读

以AtomicInteger类为例,打开源码,取两个有代表性的方法示例如下:

  1. public class AtomicInteger extends Number implements java.io.Serializable {
  2. // setup to use Unsafe.compareAndSwapInt for updates
  3. private static final Unsafe unsafe = Unsafe.getUnsafe();
  4. private static final long valueOffset;
  5. static {
  6. try {
  7. valueOffset = unsafe.objectFieldOffset
  8. (AtomicInteger.class.getDeclaredField("value"));
  9. } catch (Exception ex) { throw new Error(ex); }
  10. }
  11. private volatile int value;
  12. public final int get() {
  13. return value;
  14. }
  15. public final int getAndAdd(int delta) {
  16. return unsafe.getAndAddInt(this, valueOffset, delta);
  17. }
  18. }

unsafe:对应的是Unsafe类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。JDK中有一个类Unsafe,它提供了硬件级别的原子操作.
如下文将讲到的getIntVolatile 指的是从主存中取到int值,也就是volatile int value这个字段在堆内存中的值。
value: atomicInteger中当前值,由volatile修饰,保证内存可见性。任何其他线程对该值的修改都会即时同步到主存中使其他线程可见。
valueOffset:这里指的就是value这个属性在内存中的偏移量(内存中的地址,而不是值;value属性的地址而不是值 的地址)。

本文只讲get和set方法.

get

如上get平平无奇地返回了value值,这是因为volatile修饰保证了内存中value的值始终是最新值。

compareAndSet

因volatile无法保证原子性,因此写操作时必须要保证原子性了。
这里atomicInteger把比较和写的过程交给了Unsafe,unsafe.getAndAddInt源码如下:

  1. public final int getAndAddInt(Object atomicInteger, long valueOffset, int newValue) {
  2. int var5;
  3. do {
  4. // 从主存中获取value的最新值
  5. var5 = this.getIntVolatile(atomicInteger, valueOffset);
  6. // 拿上步取到的新值再和主存值比较,如果相同,说明上次取数到设置前没人修改该值,可以设置
  7. // 如果不同,循环尝试
  8. } while(!this.compareAndSwapInt(atomicInteger, valueOffset, var5, var5 + newValue));
  9. return var5;
  10. }

如上第4行代码,unsafe从堆中读取atomicInteger.value的实际值
第8行代码,unsafe的native方法,拿上步读取到的值和此刻主存中value的实际值比较,如果相同说明在此期间无人修改value值,可以将新值设置上去;如果不相同,重试第4第8行操作,直到设置成功 。
这种非阻塞,而是线程轮询等待资源释放的方式 叫做自旋锁。(尽管atomic没有锁,但等待资源释放也就是value可操作的过程,可以理解为对象锁)