原子类:无锁工具类的典范
相对于互斥锁的好处是性能的优化,利用CPU的硬件支持,CPU提供了CAS指令(Compare And Swap,即比较并交换)。CAS指令包含了3个参数:共享变量的内存地址A、用于比较的值B和共享变量的新值C;并当内存地址A处的值等于B时,才能将A处的值修改为C。作为一条CPU指令。
CAS相关
如下源代码释义所示,这部分主要为CAS相关操作的方法。
/**
* CAS
* @param o 包含要修改field的对象
* @param offset 对象中某field的偏移量
* @param expected 期望值
* @param update 更新值
* @return true | false
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
复制代码
什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
典型应用
CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。如下图所示,AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。
下图为某个AtomicInteger对象自增操作前后的内存示意图,对象的基地址baseAddress=“0x110000”,通过baseAddress+valueOffset得到value的内存地址valueAddress=“0x11000c”;然后通过CAS进行原子性的更新操作,成功则返回,否则继续重试,直到更新成功为止。
ABA问题
线程 T1 在执行完代码①处之后,执行代码②处之前,有可能 count 被线程 T2 更新成了 B,之后又被 T3 更新回了 A,这样线程T1 虽然看到的一直是 A,但是其实已经被其他线程更新过了,这就是 ABA 问题。
可能大多数情况下我们并不关心 ABA 问题,例如数值的原子递增,但也不能所有情况下都不关心,例如原子化的更新对象很可能就需要关心 ABA 问题,因为两个 A 虽然相等,但是第二个 A 的属性可能已经发生变化了。所以在使用 CAS 方案的时候,一定要先 check 一下。
原子类概览
- 基本数据类型:原子化i++,i–,++i,–i,也可传入函数来计算;
- 对象引用类型:解决ABA 加入标记
- 数组:原子化地更新数组里面的每一个元素。这些类提供的方法和原子化的基本数据类型的区别仅仅是:每个方法多了一个数组的索引参数;
- 对象属性更新器:利用它们可以原子化地更新对象的属性,这三个方法都是利用反射机制实现的;需要注意的是,对象属性必须是 volatile 类型的,只有这样才能保证可见性;如果对象属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 这个运行时异常。
- 累加器:相比原子化的基本数据类型,速度更快,但是不支持compareAndSet() 方法。如果你仅仅需要累加操作,使用原子化的累加器性能会更好。