跳转至

CAS详解

CAS(Compare And Swap,比较并交换)是实现乐观锁和无锁并发的底层原子操作,本质是一条“先比较再更新”的 CPU 原子指令,在 Java 中通过 Unsafe 和原子类对外暴露。

核心思想与三大参数

CAS 操作包含三个值: + V:内存中当前值(variable)。 + E:期望值(expected)。 + N:新值(new)。

执行过程:如果当前内存值 V 等于期望值 E,则把 V 更新为 N,整个比较+更新过程是一个不可分割的原子操作;如果 V ≠ E,则不做任何修改,返回失败,调用方可以选择重试或放弃。

你可以把它想象成:“如果现在冰箱里还是我刚刚看到的那瓶饮料(E),那我就把它拿走(更新为 N);如果已经不是那个状态了,什么都不做,再想别的办法。”

Java 中 CAS 的实现位置

在 Java 中,CAS 的底层实现依赖于 JVM 提供的“魔法类” Unsafe,它最终调用 CPU 的 cmpxchg 等原子指令来完成操作。

Unsafe 中典型的 CAS 方法(简化):

boolean compareAndSwapInt(Object o, long offset, int expected, int x);
boolean compareAndSwapLong(Object o, long offset, long expected, long x);
boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

JUC 里的原子类(如 AtomicInteger)内部就是基于这些方法实现的 compareAndSet、getAndIncrement 等 API。

示例:AtomicInteger 自增的典型实现逻辑(简化为伪代码):

public class MyAtomicInteger {

    private volatile int value;

    public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();           // 1. 读当前值
            int next = current + delta;    // 2. 计算新值
            // 3. CAS 尝试更新,如果失败说明有并发修改,继续自旋
            if (compareAndSet(current, next)) {
                return current;
            }
        }
    }

    public final boolean compareAndSet(int expect, int update) {
        // 内部调用 Unsafe#compareAndSwapInt
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final int get() {
        return value;
    }
}

关键点:

  • value 用 volatile 修饰,保证可见性(CAS 只保证原子性,不保证写后立刻对所有线程可见)。
  • 通过 for(;;) 或 do-while 自旋重试来保证最终更新成功。

CAS 与乐观锁、自旋的关系

CAS 是乐观锁思想的典型实现:假设冲突不多,不加互斥锁,而是在提交前检查值是否被改动,如果改动了就重试。

典型模式就是“自旋”:

while (true) {
    int oldValue = atomicInt.get();
    int newValue = oldValue + 1;
    if (atomicInt.compareAndSet(oldValue, newValue)) {
        break; // 成功
    }
    // 失败就继续循环,相当于自旋锁
}

这种方式没有阻塞、挂起和恢复线程的开销,适合临界区很短、冲突概率不太高的高并发场景。

CAS 的优点

  • 非阻塞:不需要像 synchronized 一样阻塞线程,没有锁竞争导致的上下文切换开销,通常性能更好。
  • 原子性有硬件保证:基于 CPU 指令(如 cmpxchg),天生原子,不需要额外的软件锁来保护。
  • 支撑高性能并发库:AQS、原子类、部分并发容器、LongAdder 等大量用到 CAS。

在 Java 项目中,典型用法包括:并发计数器、无锁队列、状态标记位(如一次性初始化标志)等。

CAS 的问题和局限

  1. ABA 问题 问题描述:如果一个变量从 A -> B -> A,被 CAS 的线程只看到“还是 A”,就会误以为期间没有发生过修改。

示例:

  • 线程 T1 读取 V=A,准备把它改为 C。
  • 线程 T2 把 V 从 A 改成 B,又改回 A。
  • T1 执行 CAS,发现 V 仍然是 A,于是 CAS 成功,但实际期间数据经历过中间状态 B。

解决方案:

  • 使用带“版本号/时间戳”的 CAS,如 AtomicStampedReference(值 + stamp 一起比较)。
  • 或在业务层面通过版本号字段、乐观锁机制规避。

  • 自旋开销过大

CAS 失败会继续自旋重试,如果竞争激烈,自旋次数可能很多,导致 CPU 空转严重。

因此:

  • 在冲突很频繁、临界区较大或操作复杂时,更适合用悲观锁(ReentrantLock、synchronized)。
  • 一些高级实现会采用“自旋 + 退避策略”(比如随机等待一小段时间再重试)。

  • 只能保证单个变量原子性

CAS 天然只对一个内存位置 V 生效,对多个变量的复合操作无法直接保证原子性。

常见解决方式:

  • 把多个字段封装进一个不可变对象,用 AtomicReference 进行 CAS(整体替换)。
  • 或者退回到加锁(ReentrantLock/synchronized)方案来保证更大范围的原子性。

在 Java 中如何选用 CAS / 原子类

实际项目里,你可以按这几个原则来用:

  • 简单计数或状态位(如并发访问计数、标志位):优先 AtomicInteger、AtomicBoolean 等,无需自己写 Unsafe。
  • 高并发累加场景(如统计 QPS):优先 LongAdder/LongAccumulator,内部也是基于 CAS 做分段计数,减少热点竞争。
  • 需要操作多个值的一致性:考虑 AtomicReference 封装对象 + CAS,或直接加锁。
  • 复杂业务逻辑且冲突多:尽量用互斥锁或悲观锁(包括数据库锁),避免长时间自旋。