Skip to content

CAS 核心概念

想象一个场景:你去超市存包,柜子显示"空",你放好东西关上门,转身走了。

这时候另一个顾客来了,看到柜子还是"空"的——他根本不知道你刚存了东西。

CAS 就是解决这个问题的机制。

CAS 是什么

CAS(Compare-And-Swap,比较并交换)是 CPU 提供的一种原子操作指令。在 Java 中,我们通过 sun.misc.Unsafe 封装成 compareAndSet() 方法:

java
public final boolean compareAndSet(int expectedValue, int newValue) {
    // 如果当前值等于期望值,就换成新值
    // 整个过程是原子的
    return unsafe.compareAndSwapInt(this, valueOffset, expectedValue, newValue);
}

这就像超市存包柜加了智能锁:先比较再交换

工作原理

CAS(V, A, B) 接受三个参数:

  • V:内存地址(变量在内存中的位置)
  • A:期望的旧值
  • B:想要写入的新值
┌────────────────────────────────────────────────────┐
│                    CAS 执行过程                      │
├────────────────────────────────────────────────────┤
│                                                    │
│  1. 读取 V 的当前值                                │
│         │                                          │
│         ▼                                          │
│  2. 比较:当前值 == A 吗?                          │
│         │                                          │
│         ├── 是 ──► 把 V 写成 B,返回 true           │
│         │                                          │
│         └── 否 ──► 不做任何事,返回 false           │
│                                                    │
└────────────────────────────────────────────────────┘

代码演示

模拟 CAS 实现

java
public class SimulatedCAS {

    private int value;

    public SimulatedCAS(int initialValue) {
        this.value = initialValue;
    }

    public synchronized boolean compareAndSwap(int expectedValue, int newValue) {
        if (value == expectedValue) {
            value = newValue;
            return true;
        }
        return false;
    }

    public int get() {
        return value;
    }

    public static void main(String[] args) {
        SimulatedCAS cas = new SimulatedCAS(0);

        // 成功情况
        boolean result1 = cas.compareAndSwap(0, 1);
        System.out.println("CAS(0->1): " + result1); // true
        System.out.println("当前值: " + cas.get()); // 1

        // 失败情况
        boolean result2 = cas.compareAndSwap(0, 2);
        System.out.println("CAS(0->2): " + result2); // false
        System.out.println("当前值: " + cas.get()); // 仍然是 1
    }
}

ABA 问题

这是 CAS 最著名的"坑"。

java
public class ABAProblem {

    public static void main(String[] args) {
        AtomicInteger atomic = new AtomicInteger(0);

        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            // 期望是 0,但已经被其他线程改成了 1 又改回了 0
            boolean success = atomic.compareAndSet(0, 2);
            System.out.println("线程1 CAS 结果: " + success);
        });

        Thread t2 = new Thread(() -> {
            atomic.compareAndSet(0, 1);
            atomic.compareAndSet(1, 0);
        });

        atomic.set(0);
        t1.start();
        t2.start();
    }
}

你发现了吗?线程1 看到的是"0",但这个"0"已经不是当初的"0"了——中间被改成了 1 又改回来了。这就是 ABA 问题

解决 ABA:加版本号

java
public class ABASolution {

    public static void main(String[] args) {
        AtomicStampedReference<Integer> stamped =
            new AtomicStampedReference<>(0, 0);

        int[] stamp = new int[1];

        Integer current = stamped.get(stamp);
        System.out.println("初始值: " + current + ", 版本: " + stamp[0]);

        // 修改时同时更新版本号
        stamped.compareAndSet(0, 1, stamp[0], stamp[0] + 1);
        stamped.compareAndSet(1, 0, stamp[0], stamp[0] + 1);

        Integer finalValue = stamped.get(stamp);
        System.out.println("最终值: " + finalValue + ", 版本: " + stamp[0]);
        // 版本号已经是 2 了,线程1 用旧版本号(0) CAS 会失败
    }
}

自旋实现递增

CAS + 循环 = 自旋锁。这是 AtomicInteger 内部实现 incrementAndGet() 的方式:

java
public class CASSpinDemo {

    private final AtomicInteger value = new AtomicInteger(0);

    public void increment() {
        int expected;
        do {
            expected = value.get();
        } while (!value.compareAndSet(expected, expected + 1));
    }

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

    public static void main(String[] args) throws InterruptedException {
        CASSpinDemo demo = new CASSpinDemo();
        int threadCount = 100;

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            }).start();
        }

        Thread.sleep(2000);
        System.out.println("结果: " + demo.get()); // 应该是 100000
    }
}

CAS vs synchronized

特性synchronizedCAS
阻塞阻塞(悲观)非阻塞(乐观)
实现操作系统MutexCPU原子指令
性能低竞争时还行无竞争时极快
复杂度简单需处理失败重试
粒度方法/代码块单个变量

注意事项

  1. ABA 问题:值变回原值 CAS 无法检测,用 AtomicStampedReference 解决
  2. 自旋开销:高竞争下可能导致 CPU 空转,LongAdder 就是为了解决这个问题
  3. 只能保证单个变量:复合操作需要配合其他机制
  4. 不保证公平性:先到不一定先得

要点回顾

CAS 是乐观锁的典型实现:先假设没有冲突,失败了再重试。

synchronized ──► 我先锁上,干完再解锁(悲观)
CAS ──► 我先试试,不行就重来(乐观)

JUC 中的原子类(AtomicInteger、AtomicReference 等)底层都是用 CAS 实现的。

基于 VitePress 构建