CAS
什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。
CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法底层实现即为CPU指令cmpxchg。
Unsafe 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);
VMSupportsCS8
compareAndSwapInt 和 compareAndSwapLong 在 java 层面是非常相似的, 但是 C++ 底层实现相差很多
在 /jdk8u-dev/hotspot/src/share/vm/prims/unsafe.cpp 中定义了 CAS 中的方法 UNSAFE_ENTRY
相比于 compareAndSwapInt 方法,compareAndSwapLong 方法包含了 条件编译 SUPPORTS_NATIVE_CX8
即方法首先会判断是否支持 SUPPORTS_NATIVE_CX8 ,就是判断机器是否支持 8 字节的 cmpxchg 这个 CPU 指令。如果硬件不支持,就会判断 JVM 是否支持,如果 JVM 也不支持,就表明这个操作不是 Lock Free 的。此时JVM 会使用显示锁例如 synchronized 来接管
在 AtomicLong.java 中也能看到如下定义
// 记录底层 JVM 是否支持 long 的无锁 compareAndSwap。虽然 Unsafe.compareAndSwapLong 方法在任何一种情况下都有效,但应该在 Java 级别处理一些构造以避免锁定用户可见的锁。
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
.. 返回底层 JVM 是否支持 long 的无锁 CompareAndSet。仅调用一次并缓存在 VM_SUPPORTS_LONG_CAS 中
private static native boolean VMSupportsCS8();
32 位 CPU 肯定不支持 8 字节 64 位数字的 cpmxchg 指令
AtomicLong 的 lazySet 使用了 Unsafe.putOrderedLong 方法, 同样有这个判断
Unsafe 无锁 CAS+自旋 原子操作
Unsafe 中还提供了基于 CAS 的自旋无锁原子操作如下
// 获取旧值, 并设置新值
public final int getAndSetInt(Object o, long offset, int newValue) {
}
public final long getAndSetLong(Object o, long offset, long newValue) {
}
public final Object getAndSetObject(Object o, long offset, Object newValue) {
}
// 获取旧值, 并给旧值增加delta
public final int getAndAddInt(Object o, long offset, int delta) {
}
public final long getAndAddLong(Object o, long offset, long delta) {
}
// 通过CAS+自旋的方式, 保证获取旧值和设置新值两个操作合并, 使其具有原子操作的效果
public final int getAndSetInt(Object o, long offset, int newValue) {
int value;
do {
// 获取到最新的value
value = this.getIntVolatile(o, offset);
// 原子指令, 比较期望值与更新值
// 当两者相同时, 说明获取value后到cas操作间value被修改过了, 修改旧值为新值, 返回true, while条件为false, 结束循环
// 当两者不同时, 说明获取value后到cas操作间value没被修改过, 直接返回false, while条件为true, 重新获取最新的value并cas, 直到成功
} while(!this.compareAndSwapInt(o, offset, value, newValue));
// 返回旧值
return value;
}
Unsafe 无锁 CAS+自旋 原子操作 的弊端
- ABA问题: a线程获取到value为1, b线程将value设置为2, c线程将value又设置为1, a线程cas操作, 期望值和原值value相同, 认为value没有被修改过, 操作成功, 但其实value被修改过了. 在某些情况下可能存在问题
- 自旋时间过长: 当并发高的时候, 原值可能会被频繁修改, 导致很多线程需要大量自旋, 消耗大量cpu资源, 损耗性能. 所以CAS+自旋不太适用于超高并发场景
典型应用
CAS 在 java.util.concurrent.atomic 相关类、Java AQS、ConcurrentHashMap 等实现上有非常广泛的应用