CAS 的原理是什么?如何解决 ABA 问题?

结论先行:CAS(Compare And Swap)是一条硬件原语:仅当内存当前值等于期望值时,才把它更新为目标值,比较与交换在一条指令内原子完成。它让无锁并发成为可能,但存在 ABA 问题——值先被改成 B 又被改回 A,CAS 无法察觉中间发生过变化。

CAS 核心要点

  1. 三个操作数:内存位置 V、期望值 A、新值 B;当 V == A 时把 V 更新为 B,并返回是否成功。
  2. 原子性由 CPU 指令(如 cmpxchg)保证,Java 通过 Unsafe 的 compareAndSwap 系列暴露。
  3. 典型实现:java.util.concurrent.atomic 包(AtomicInteger、AtomicReference、AtomicStampedReference)。
  4. 失败语义:CAS 失败只说明“当前值 ≠ 期望值”,调用方通常循环重试,即自旋。
  5. 硬件保证:依赖 CPU 的 cmpxchg 类指令,Java 侧配合 volatile 读完成无锁更新。
衍生问题表现常用对策
ABAA→B→A,CAS 误判“从未被改过”AtomicStampedReference 携带版本号
自旋开销高并发下空转烧 CPU限制自旋次数、指数退避
单变量限制一次只能操作一个变量AtomicReference 把多个字段打包成一个对象

使用前提:无锁编程适合“冲突概率低、临界区极短”的场景;高冲突时自旋反而烧 CPU,不如直接加锁。

import java.util.concurrent.atomic.AtomicStampedReference;

public class AbaDemo {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> ref =
                new AtomicStampedReference<>(100, 0); // (值, 版本戳)

        int stamp = ref.getStamp();
        int value = ref.getReference();
        // 值、版本都匹配才更新,版本 +1
        boolean ok = ref.compareAndSet(value, 200, stamp, stamp + 1);
        System.out.println("更新结果: " + ok);
    }
}

常见追问 / 记忆点

  • 记忆点:CAS 三要素“位置、期望值、新值”,ABA 用版本戳(stamp)破解。
  • 追问:LongAdder 把热点 value 拆成 Cell 数组分散竞争,是高并发计数下对 CAS 的优化。
  • 追问:CAS 只能保证单个共享变量的原子性,复合操作要么合并变量、要么仍需加锁。
  • 追问:AtomicMarkableReference 只关心“是否被改过”,用布尔位即可,代价比版本号更低。
笔记加载中…