Java 中的 CAS 操作
在Java中,锁在并发处理中占据了一席之地,但是使用锁有一个不好的地方,就是当一个线程没有获取到锁时会被阻塞挂起,这会导致线程上下文的切换和重新调度开销。Java提供了非阻塞的volatile 关键字来解决共享变量的可见性问题,这在一定程度上弥补了锁带来的开销问题,但是 volatile 只能保证共享变量的可见性,不能解决读一改一写等的原子性问题。CAS即Compare and Swap,其是JDK提供的非阻塞原子性操作,它通过硬件保证了比较一更新操作的原子性。JDK里面的Unsafe类提供了一系列的compareAndSwap*方法,下面以compareAndSwapLong方法为例进行简单介绍。
- boolean compareAndSwapLong(Object obj,long valueOffset,long expect, long update) 方法:其中compareAndSwap的意思是比较并交换。CAS有四个操作数,分别为:对象内存位置、对象中的变量的偏移量、变量预期值和新的值。其操作含义是,如果对象 obj中内存偏移量为 valueOfset的变量值为expect,则使用新的值update 替换旧的值expect。这是处理器提供的一个原子性指令。
关于CAS操作有个经典的ABA问题,具体如下:假如线程I使用CAS修改初始值为A的变量X,那么线程1会首先去获取当前变量X的值(为A),然后使用CAS操作尝试修改X的值为B,如果使用CAS操作成功了,那么程序运行一定是正确的吗?其实未必,这是因为有可能在线程1获取变量X的值A后,在执行CAS前,线程Ⅱ使用CAS修改了变量X的值为B,然后又使用CAS修改了变量X的值为A。所以虽然线程I执行CAS时X的值是 A,但是这个A已经不是线程】获取时的A了。这就是 ABA 问题。
ABA 问题的产生是因为变量的状态值产生了环形转换,就是变量的值可以从A到B,然后再从 B到A。如果变量的值只能朝着一个方向转换,比如A到B,B到C,不构成环形,就不会存在问题。JDK中的AtomicStampedReference 类给每个变量的状态值都配备了一个时间戳,从而避免了 ABA 问题的产生。