在 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 中内存偏移量为 valueOffset 的变量值为 expect,则使用新的值 update 替换旧的值 expect。这是处理器提供的一个原子性指令。
关于 CAS 操作有个经典的 ABA 问题,具体如下:假如线程 I 使用 CAS 修改初始值为 A 的变量 X,那么线程 I 会首先去获取当前变量 X 的值(为 A),然后使用 CAS 操作尝试修改 X 的值为 B,如果使用 CAS 操作成功了,那么程序运行一定是正确的吗?其实未必,这是因为有可能在线程 I 获取变量 X 的值 A 后,在执行 CAS 前,线程 II 使用 CAS 修改了变量 X 的值为 B,然后又使用 CAS 修改了变量 X 的值为 A。所以虽然线程 I 执行 CAS 时 X 的值是 A,但是这个 A 已经不是线程 I 获取时的 A 了。这就是 ABA 问题。
ABA 问题的产生是因为变量的状态值产生了环形转换,就是变量的值可以从 A 到 B,然后再从 B 到 A。如果变量的值只能朝着一个方向转换,比如 A 到 B,B 到 C,不构成环形,就不会存在问题。JDK 中的 AtomicStampedReference 类给每个变量的状态值都配备了一个时间戳,从而避免了 ABA 问题的产生。