CAS 核心概念
想象一个场景:你去超市存包,柜子显示"空",你放好东西关上门,转身走了。
这时候另一个顾客来了,看到柜子还是"空"的——他根本不知道你刚存了东西。
CAS 就是解决这个问题的机制。
CAS 是什么
CAS(Compare-And-Swap,比较并交换)是 CPU 提供的一种原子操作指令。在 Java 中,我们通过 sun.misc.Unsafe 封装成 compareAndSet() 方法:
java
public final boolean compareAndSet(int expectedValue, int newValue) {
// 如果当前值等于期望值,就换成新值
// 整个过程是原子的
return unsafe.compareAndSwapInt(this, valueOffset, expectedValue, newValue);
}这就像超市存包柜加了智能锁:先比较再交换。
工作原理
CAS(V, A, B) 接受三个参数:
- V:内存地址(变量在内存中的位置)
- A:期望的旧值
- B:想要写入的新值
┌────────────────────────────────────────────────────┐
│ CAS 执行过程 │
├────────────────────────────────────────────────────┤
│ │
│ 1. 读取 V 的当前值 │
│ │ │
│ ▼ │
│ 2. 比较:当前值 == A 吗? │
│ │ │
│ ├── 是 ──► 把 V 写成 B,返回 true │
│ │ │
│ └── 否 ──► 不做任何事,返回 false │
│ │
└────────────────────────────────────────────────────┘代码演示
模拟 CAS 实现
java
public class SimulatedCAS {
private int value;
public SimulatedCAS(int initialValue) {
this.value = initialValue;
}
public synchronized boolean compareAndSwap(int expectedValue, int newValue) {
if (value == expectedValue) {
value = newValue;
return true;
}
return false;
}
public int get() {
return value;
}
public static void main(String[] args) {
SimulatedCAS cas = new SimulatedCAS(0);
// 成功情况
boolean result1 = cas.compareAndSwap(0, 1);
System.out.println("CAS(0->1): " + result1); // true
System.out.println("当前值: " + cas.get()); // 1
// 失败情况
boolean result2 = cas.compareAndSwap(0, 2);
System.out.println("CAS(0->2): " + result2); // false
System.out.println("当前值: " + cas.get()); // 仍然是 1
}
}ABA 问题
这是 CAS 最著名的"坑"。
java
public class ABAProblem {
public static void main(String[] args) {
AtomicInteger atomic = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 期望是 0,但已经被其他线程改成了 1 又改回了 0
boolean success = atomic.compareAndSet(0, 2);
System.out.println("线程1 CAS 结果: " + success);
});
Thread t2 = new Thread(() -> {
atomic.compareAndSet(0, 1);
atomic.compareAndSet(1, 0);
});
atomic.set(0);
t1.start();
t2.start();
}
}你发现了吗?线程1 看到的是"0",但这个"0"已经不是当初的"0"了——中间被改成了 1 又改回来了。这就是 ABA 问题。
解决 ABA:加版本号
java
public class ABASolution {
public static void main(String[] args) {
AtomicStampedReference<Integer> stamped =
new AtomicStampedReference<>(0, 0);
int[] stamp = new int[1];
Integer current = stamped.get(stamp);
System.out.println("初始值: " + current + ", 版本: " + stamp[0]);
// 修改时同时更新版本号
stamped.compareAndSet(0, 1, stamp[0], stamp[0] + 1);
stamped.compareAndSet(1, 0, stamp[0], stamp[0] + 1);
Integer finalValue = stamped.get(stamp);
System.out.println("最终值: " + finalValue + ", 版本: " + stamp[0]);
// 版本号已经是 2 了,线程1 用旧版本号(0) CAS 会失败
}
}自旋实现递增
CAS + 循环 = 自旋锁。这是 AtomicInteger 内部实现 incrementAndGet() 的方式:
java
public class CASSpinDemo {
private final AtomicInteger value = new AtomicInteger(0);
public void increment() {
int expected;
do {
expected = value.get();
} while (!value.compareAndSet(expected, expected + 1));
}
public int get() {
return value.get();
}
public static void main(String[] args) throws InterruptedException {
CASSpinDemo demo = new CASSpinDemo();
int threadCount = 100;
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
}).start();
}
Thread.sleep(2000);
System.out.println("结果: " + demo.get()); // 应该是 100000
}
}CAS vs synchronized
| 特性 | synchronized | CAS |
|---|---|---|
| 阻塞 | 阻塞(悲观) | 非阻塞(乐观) |
| 实现 | 操作系统Mutex | CPU原子指令 |
| 性能 | 低竞争时还行 | 无竞争时极快 |
| 复杂度 | 简单 | 需处理失败重试 |
| 粒度 | 方法/代码块 | 单个变量 |
注意事项
- ABA 问题:值变回原值 CAS 无法检测,用 AtomicStampedReference 解决
- 自旋开销:高竞争下可能导致 CPU 空转,LongAdder 就是为了解决这个问题
- 只能保证单个变量:复合操作需要配合其他机制
- 不保证公平性:先到不一定先得
要点回顾
CAS 是乐观锁的典型实现:先假设没有冲突,失败了再重试。
synchronized ──► 我先锁上,干完再解锁(悲观)
CAS ──► 我先试试,不行就重来(乐观)JUC 中的原子类(AtomicInteger、AtomicReference 等)底层都是用 CAS 实现的。
