CAS 的三大缺点与解决方案¶
一、ABA 问题¶
问题¶
线程 1 读 A,线程 2 把 A 改成 B 再改回 A,线程 1 CAS 成功——但中间值变过。
如果业务关心"值是否变过"(比如栈顶指针),ABA 会出问题。
解决:AtomicStampedReference¶
加版本号:
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(1, 0);
// CAS 时比较版本号
ref.compareAndSet(1, 2, 0, 1);
每次改版本号 +1,版本不对就失败。
二、自旋浪费 CPU¶
问题¶
竞争激烈时,CAS 一直失败,do-while 空转,浪费 CPU。
解决¶
- 竞争不激烈:CAS 合适。
- 竞争激烈:用
synchronized或 AQS(阻塞挂起,不占 CPU)。 - JDK 8 的 LongAdder:分段 CAS,减少竞争。
三、只能保证一个变量的原子性¶
问题¶
CAS 只能改一个内存地址。要同时改两个变量,CAS 做不到。
解决¶
- 用锁。
- 把多个变量打包成一个对象,用
AtomicReference包整个对象。
四、对比总结¶
| 缺点 | 场景 | 方案 |
|---|---|---|
| ABA | 值变过又变回来有意义 | AtomicStampedReference |
| 自旋开销 | 高竞争 | synchronized / AQS |
| 单变量 | 多变量原子性 | 锁 / AtomicReference |
LongAdder
JDK 8 引入,把一个 value 分成多个 Cell,不同线程 CAS 不同 Cell,最后 sum。减少竞争,高并发下比 AtomicLong 快。