Skip to content

CAS 优缺点

乐观锁和悲观锁,你站哪一边?

synchronized ──► 悲观派:先加锁再干活,万一有人跟我抢呢
CAS ──► 乐观派:先试试,没人抢我就赢了,有人抢就重来

没有银弹,只有权衡。

CAS 的优势

1. 非阻塞

没有锁的挂起和唤醒,线程不会进入阻塞状态。

java
public class CASAdvantageDemo {

    private final AtomicInteger counter = new AtomicInteger(0);

    // CAS:无锁累加
    public void increment() {
        int expected;
        do {
            expected = counter.get();
        } while (!counter.compareAndSet(expected, expected + 1));
    }

    // synchronized:有锁累加
    private int syncCounter = 0;
    public synchronized void syncIncrement() {
        syncCounter++;
    }

    public static void main(String[] args) throws InterruptedException {
        CASAdvantageDemo demo = new CASAdvantageDemo();

        // 测试 CAS
        long start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            demo.increment();
        }
        System.out.println("CAS 耗时: " + (System.nanoTime() - start) / 1_000_000 + "ms");

        // 测试 synchronized
        start = System.nanoTime();
        for (int i = 0; i < 1_000_000; i++) {
            demo.syncIncrement();
        }
        System.out.println("synchronized 耗时: " + (System.nanoTime() - start) / 1_000_000 + "ms");
    }
}

在低到中等竞争下,CAS 通常更快。

2. 无死锁

锁的最坏情况是死锁——两个线程互相等待对方释放资源。

CAS 不存在这个问题,因为它只操作一个变量。要么成功,要么重来,没有"占有并等待"的可能。

3. 性能优异

无竞争时,CAS 就是一条 CPU 指令,毫秒级完成。

CAS 的劣势

1. ABA 问题

值从 A 变成 B 又变回 A,CAS 无法检测。

java
public class ABAProblemDemo {

    private final AtomicReference<String> ref = new AtomicReference<>("A");

    public static void main(String[] args) throws InterruptedException {
        ABAProblemDemo demo = new ABAProblemDemo();

        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            boolean success = demo.ref.compareAndSet("A", "C");
            System.out.println("线程1 CAS 结果: " + success);
        });

        Thread t2 = new Thread(() -> {
            demo.ref.compareAndSet("A", "B");
            demo.ref.compareAndSet("B", "A");
        });

        t2.start();
        Thread.sleep(50);
        t1.start();

        t1.join();
        t2.join();

        System.out.println("最终值: " + demo.ref.get());
        System.out.println("问题:线程1 以为读到了 A,但 A 已被其他线程修改过");
    }
}

2. 高竞争下的自旋

当大量线程同时竞争同一个变量时,CAS 会频繁失败,CPU 空转。

java
public class CASContentionDemo {

    private final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        CASContentionDemo demo = new CASContentionDemo();
        ExecutorService executor = Executors.newFixedThreadPool(100);

        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.counter.incrementAndGet();
                }
            });
        }

        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);

        System.out.println("结果: " + demo.counter.get());
        System.out.println("高竞争下,CAS 可能导致大量自旋重试");
    }
}

解决方案:LongAdder 分段累加,减少竞争。

3. 复合操作需要循环 CAS

简单的 value++ 用 CAS 实现:

java
// ❌ 不原子
public boolean incrementIfEven() {
    int current = value.get();
    if (current % 2 == 0) {
        return value.compareAndSet(current, current + 1);  // 检查和修改之间可能被修改
    }
    return false;
}

// ✅ 原子
public boolean incrementIfEvenCorrect() {
    while (true) {
        int current = value.get();
        if (current % 2 != 0) {
            return false;
        }
        if (value.compareAndSet(current, current + 1)) {
            return true;
        }
        // CAS 失败,重试
    }
}

4. 只能保证单个变量

java
// ❌ 这不是原子操作
public void transfer(Account from, Account to, int amount) {
    if (from.balance >= amount) {
        from.balance -= amount;   // 非原子
        to.balance += amount;     // 非原子
    }
}

// 需要加锁
public synchronized void transfer(...) { ... }

实战选择

java
public class CASVsSyncSelection {

    // 低竞争:CAS 更快
    private final AtomicInteger lowContention = new AtomicInteger(0);

    // 高竞争复合操作:synchronized
    private final Object highContentionLock = new Object();
    private int highContentionValue = 0;

    public void simpleIncrement() {
        lowContention.incrementAndGet();  // CAS
    }

    public synchronized void complexOperation() {
        // 多个变量需要保持一致,必须用锁
    }
}

总结对比

特性CASsynchronized
阻塞非阻塞阻塞
死锁可能
性能无竞争时极快有竞争时稳定
复杂度需处理重试简单
粒度单变量任意代码块
ABA需额外处理不存在
选择原则:

简单计数 ──► AtomicLong / LongAdder
标志位 ──► AtomicBoolean
引用更新 ──► AtomicReference
高竞争复合操作 ──► synchronized
低竞争复合操作 ──► 循环 CAS
需要版本追踪 ──► AtomicStampedReference

记住:没有最好的方案,只有最适合当前场景的方案。

基于 VitePress 构建