外观
一句话答案
CAS 比较并交换(Compare And Swap)是 CPU 原子指令,ABA 问题用版本号/AtomicStampedReference 解决。
核心要点
CAS(Compare And Swap):
- 三个操作数:内存位置 V(当前值)、期望值 A、新值 B
- 如果 V == A,则将 V 改为 B,返回 true;否则不修改,返回 false
- 整个操作是原子的(硬件层面保证)
底层实现:CPU 指令 cmpxchg(Compare and Exchange)
java
// Java AtomicInteger 的实现
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// unsafe 调用 JNI 最终执行 CPU 的 cmpxchg 指令
// 多核 CPU 上加 lock 前缀,保证原子性CAS 的三个问题:
1. ABA 问题
- 值从 A → B → A,CAS 认为没变化,但中间其实变过
- 解决:
AtomicStampedReference(附加版本号/时间戳)
2. 自旋开销
- CAS 失败后循环重试(自旋),高并发下 CPU 空转严重
- 解决:限制自旋次数,或换用 LongAdder(分段 CAS)
3. 只能保证一个变量的原子性
- 需要原子操作多个变量时,CAS 无法直接解决
- 解决:
AtomicReference<Object>(将多个变量封装成对象),或换用 synchronized
追问与易错
追问方向:
- CAS 底层怎么实现的?(Unsafe 类调用 CPU 的 cmpxchg 指令)
- ABA 问题有什么实际危害?(链表/栈操作可能导致数据丢失)
- CAS 自旋失败一直重试怎么办?(有自适应自旋,超过阈值升级锁)
易错点:
- ❌ "CAS 是无锁的所以没有开销"——自旋消耗 CPU,竞争激烈时不如锁
- ❌ "ABA 问题不常见可以忽略"——在无锁栈/链表等场景会有实际问题
💡 记忆锚点
CAS 是"先看后改"的原子操作:我记住你是 A,改的时候发现还是 A 才动手,否则从头来。ABA 坑在于东西被换走又换回来,你以为没变其实变过——加个版本号(AtomicStampedReference)就像给每次修改盖个章,章号对不上就知道动过手脚。