CAS是什么?

112 阅读3分钟

CAS是什么

一句话总结:

CAS = volatile 的可见性 + CPU 原子指令的原子性
让“读 → 改 → 写”三步操作变成一步不可分割的硬件操作,从而实现无锁线程安全。


一、为什么需要 CAS?—— volatile 的局限

回顾这个经典问题:

volatile int count = 0;
count++; // 线程不安全!

❌ 问题在哪?

虽然 countvolatile,但 count++ 仍是三步:

  1. int tmp = count;
  2. tmp = tmp + 1;
  3. count = tmp;

即使每一步都可见,多个线程仍可能同时读到旧值,导致覆盖。

🎯 我们真正想要的是:

“如果当前值是 A,就把它改成 B;否则失败重试。”

这正是 CAS 的语义。


二、CAS 是什么?—— 图解硬件原子指令

🧠 核心思想:

“先检查,再更新”必须是一体的,中间不能被打断。

📈 执行流程图(以 AtomicInteger.incrementAndGet() 为例):

线程A 和 线程B 同时执行 count++

初始:count = 0(volatile,主内存)

线程A:                     线程B:
1. 读 count → 0             1. 读 count → 0
2. 计算 0+1 = 1             2. 计算 0+1 = 1
3. CAS(0, 1)                3. CAS(0, 1)
   │                          │
   └──► CPU 原子指令:         └──► CPU 原子指令:
        "如果当前值==0,就设为1"     "如果当前值==0,就设为1"
        → 成功!count=1          → 失败!因为 count 已是 1

线程B 失败后重试:
4. 读 count → 1
5. 计算 1+1 = 2
6. CAS(1, 2) → 成功!count=2

结果正确:count = 2

🔑 关键:CAS 是 CPU 级别的原子指令(如 x86 的 cmpxchg),
在执行期间,其他 CPU 无法修改该内存地址


三、Java 如何暴露 CAS?—— 从 Unsafe 到 VarHandle

1. 底层:sun.misc.Unsafe(JDK 8 及以前)

// AtomicInteger 内部
unsafe.compareAndSwapInt(this, valueOffset, expect, update);

2. 现代:java.lang.invoke.VarHandle(JDK 9+)

VarHandle.handle().compareAndSet(obj, expected, new);

3. 封装:java.util.concurrent.atomic

  • AtomicInteger
  • AtomicReference
  • AtomicLong
  • AtomicBoolean
  • ……

💡 你不需要直接调用 UnsafeAtomicXXX 就够了


四、CAS 的三大要素(必须同时满足)

要素说明Java 实现
1. volatile 字段保证每次读取都是最新值AtomicInteger 内部 volatile int value
2. 原子 CAS 指令硬件保证“比较+赋值”不可分割Unsafe.compareAndSwapInt()
3. 自旋重试机制失败后循环重试,直到成功while (!cas(...)) { }

🌰 AtomicInteger.incrementAndGet() 伪代码:

public final int incrementAndGet() {
    for (;;) {
        int current = get(); // volatile 读
        int next = current + 1;
        if (compareAndSet(current, next)) // CAS
            return next;
        // 失败?继续循环重试
    }
}

五、CAS 的优势 vs synchronized

对比项synchronizedCAS(AtomicXXX)
锁机制悲观锁(阻塞线程)乐观锁(无锁,失败重试)
性能高竞争下开销大(线程挂起/唤醒)低竞争下极快(纯用户态)
适用场景复杂逻辑、多步骤操作简单状态更新(计数器、标志位)
公平性可能饥饿无公平性,但高吞吐

原则

  • 简单原子操作 → 用 AtomicXXX
  • 复杂临界区 → 用 synchronized

六、CAS 的局限:ABA 问题

❓ 什么是 ABA?

  • 线程A 读到值 A;
  • 线程B 将 A → B → A;
  • 线程A 执行 CAS(A, C) → 成功;
  • 中间状态已变,逻辑可能错误。

🌰 例子:栈顶指针

初始:栈顶 = A
线程1:准备 pop A
线程2:pop A → push B → pop B → push A
线程1:CAS(A, null) → 成功,但栈已被篡改!

✅ 解决方案:带版本号的 CAS

  • AtomicStampedReference
  • AtomicMarkableReference

💡 大多数场景(如计数器)ABA 无影响,无需过度担心。


七、何时使用 CAS?—— 三类典型场景

场景示例推荐类
计数器QPS 统计、请求计数AtomicLong
状态标志切换开关、状态机AtomicBoolean / AtomicReference<Enum>
无锁数据结构队列、栈、链表ConcurrentLinkedQueue(内部用 CAS)