CAS: (Compare And Swap) 比较和交换, Java 就是通过 CAS 来实现原子操作的 !
CAS 需要三个操作数, 分别是 : 内存地址, 旧的预期值, 准备设置的新值
CAS执行时, 对于一个变量 V, 他的旧的预期值为 A, 将要修改的值为 B, 有且仅当 V 符合 A, 处理器才会将 A 换成 B, 否者操作失败.
在 JDK 5之后, Java 类库才开始使用 CAS 操作, 该操作被封装在 sun.misc.Unsafe 类中, 如果你用过 java.util.concurrent.atomic 包中的类, 那么你一定对它不陌生, 所有的原子操作类, 底部调用的都是 Unsafe 的API
由于 Unsafe 并没有开源, 但是可以使用 IDEA 进行反编译看到源码 :
// UnSafe 类提供的方法, 用于 CAS 操作, 传入三个参数,
// 依次为: 要操作的对象, 旧的预期值, 和需要设置的新值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取 var1 的最新值
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
从源码中可以看出, 采用的是循环 CAS, 需要先获取变量的最新值, 如果这个最新值等于旧的预期值, 那就将其交换并成功返回
CAS可以有效的提升并发的效率,但同时也会引入一些问题
如果变量的初值为 A, 将要修改之前检查的值仍然为 A , 就能说明这个变量没有被其他线程改变过吗?
有可能在这个过程中, 其他线程将 A 换成 B, 但最后又被改为 A, 那么CAS操作就会认为这个值从来没有改变过. 这就是 ABA 问题(大部分情况下ABA问题不会影响程序并发的正确性)
如果线程竞争激烈, 有可能存在一些线程长时间无法操作成功, 会给 CPU 带来非常大的开销
对于多个共享变量操作时, 一次 CAS 就无法保证其原子性了
ABA 问题可以为变量添加一个版本号或者时间戳来保证 CAS 的正确性, java.util.concurrent 包提供了一个带有标记的原子引用类
AtomicStampedReference
不过这个类处于比较鸡肋的位置, 大部分情况下 ABA 问题并不会影响程序并发的正确性
对于消耗 CPU 的问题, 如果 JVM 支持 CPU 的
pause
指令, 那么效率会有一定的提升
如何解决同时操作单一变量的问题, 可以将这些变量封装在一个对象中, 使用原引用类
AtomicReference
来保证原子性, 这样就能同时操作多个变量啦 !
使用 java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
可以反汇编生成汇编指令 (需要安装 HSDIS 插件) , 通过查看 HotSpot 的 C/C++ 源码, 也能查看其底层汇编实现
cmpxchg
来实现 CAScasa
指令实现ldrex/strex
指令完成 LL/SC 的功能目前主流 CPU 就是 x86 架构的 Intel, 所有基本上都是用 cmpxchg
实现的 CAS, 但是这依然不是一个原子指令, 仍然需要加上 lock
前缀, 通过锁定 北桥电信号
(不采用锁总线) 实现原子指令, 所以, CAS 最终的底层实现是 lock cmpxchg
汇编指令, 该指定为 原子指令