跳转至

CAS 的三大缺点与解决方案

一、ABA 问题

问题

线程 1 读 A,线程 2 把 A 改成 B 再改回 A,线程 1 CAS 成功——但中间值变过。

线程 1: 读 a=1
线程 2: a=1 → 2 → 1
线程 1: CAS(a, 1, 3) 成功

如果业务关心"值是否变过"(比如栈顶指针),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 做不到。

// 想同时改 a 和 b,CAS 做不到
cas(a, expectA, newA);
cas(b, expectB, newB);   // 第二步可能失败

解决

  • 用锁。
  • 把多个变量打包成一个对象,用 AtomicReference 包整个对象。
class Pair { int a; int b; }
AtomicReference<Pair> pairRef = new AtomicReference<>();

四、对比总结

缺点 场景 方案
ABA 值变过又变回来有意义 AtomicStampedReference
自旋开销 高竞争 synchronized / AQS
单变量 多变量原子性 锁 / AtomicReference

LongAdder

JDK 8 引入,把一个 value 分成多个 Cell,不同线程 CAS 不同 Cell,最后 sum。减少竞争,高并发下比 AtomicLong 快。