Java多线程中的CAS(Compare-And-Swap)
CAS 是 Java 并发编程的核心基石之一,全称 Compare-And-Swap(比较并交换),是一种**无锁(lock-free)**的原子操作。
一、CAS 的核心思想
CAS 包含三个操作数:
| 参数 | 含义 |
|---|---|
| V (Value) | 内存中的当前值 |
| A (Expected) | 期望的旧值 |
| B (New) | 要更新的新值 |
执行逻辑:
当且仅当内存值 V == 期望值 A 时,才把内存值更新为 B;否则不做任何操作。整个"比较+更新"是一个原子操作 ,由 CPU 指令保证(如 x86 的
cmpxchg)。
if (V == A) {
V = B;
return true;
} else {
return false;
}
二、Java 中的 CAS 实现
1. Unsafe 类(底层)
Java 的 CAS 最终都依赖于 sun.misc.Unsafe 提供的 native 方法:
java
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
这些方法通过 JNI 调用底层 CPU 的 CAS 指令。
2. 原子类(AtomicXXX)
java.util.concurrent.atomic 包下的类是最典型的 CAS 应用:
java
public class AtomicInteger extends Number {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; // value 字段的内存偏移量
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 典型用法:自增
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
注意 :
value用volatile修饰,保证可见性;valueOffset通过反射获取字段内存地址。
3. 自旋 + CAS 模式
以 getAndAddInt 为例,展示经典的自旋 CAS 写法:
java
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset); // 读取最新值
} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS 失败则重试
return v;
}
三、CAS 的三大问题
1. ABA 问题 ⭐
问题:线程1读到值为 A,准备 CAS 改为 C;期间线程2把 A→B→A。线程1的 CAS 仍然成功,但值已经被"偷偷"改过。
解决 :使用 版本号 / 时间戳 ,即 AtomicStampedReference:
java
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
int stamp = ref.getStamp();
ref.compareAndSet(100, 200, stamp, stamp + 1); // 同时校验值和版本号
类似的还有 AtomicMarkableReference(用一个 boolean 标记)。
2. 自旋开销大 ⭐
CAS 失败会一直循环重试。在高并发竞争激烈时,大量线程空转消耗 CPU。
优化:
LongAdder/DoubleAdder(分段累加,减少竞争)- 适当退让(
Thread.yield())或加锁策略 - JDK 内部会调用
pause指令降低流水线开销
3. 只能保证单个变量的原子性
CAS 只对一个内存位置有效。若需多个变量原子更新:
解决 :使用 AtomicReference 封装一个对象:
java
class Point { int x, y; }
AtomicReference<Point> pointRef = new AtomicReference<>(new Point());
// 用 CAS 整体替换引用(每次创建新对象,避免可变状态)
pointRef.compareAndSet(oldPoint, new Point(1, 2));
四、CAS 的优缺点
| 优点 | 缺点 |
|---|---|
| 无锁,无阻塞,性能高 | ABA 问题 |
| 避免线程上下文切换 | 高竞争时自旋消耗 CPU |
| 由硬件指令保证原子性 | 只能保证单变量原子性 |
| 适合读多写少场景 | 代码复杂,不易理解调试 |
五、CAS 的典型应用场景
- 原子类 :
AtomicInteger、AtomicLong、AtomicReference - AQS(AbstractQueuedSynchronizer) :
ReentrantLock、Semaphore等的state字段更新 - 并发容器 :
ConcurrentHashMap的节点插入(部分用 CAS) - LongAdder 的分段计数
- 无锁数据结构:无锁队列、无锁栈
六、总结(一句话)
CAS 是通过硬件原子指令实现"比较并交换"的无锁并发原语,是 Java 原子类和 AQS 的底层基石;使用时需注意 ABA 问题、自旋开销和单变量限制。