Skip to content

CAS 学习

CAS 三要素

  • 内存值
  • 期望值(旧值)
  • 新值

语义:Compare-And-Swap 如果读取变量的内存值 = 期望值,那么设置内存值为新值,否则重新读取内存值继续下一轮循环

在 Java 中通常会使用原子类来实现 CAS 功能,比如 AtomicIntegerAtomicLongAtomicReference

java
public class CASDemo {
    private static final AtomicInteger counter = new AtomicInteger(0);

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

        for (int i = 0; i < 1000; i++) {
            executor.submit(counter::incrementAndGet);
        }

        executor.shutdown();
        boolean b = executor.awaitTermination(1, TimeUnit.MINUTES);
        System.out.println("最终计数: " + counter.get()); // 必定 = 1000
    }
}

CAS 与 synchronized 对比

对比维度CASsynchronized
实现方式CPU 原子指令(用户态)重量级锁的实现方式是 OS Mutex (内核态)
阻塞自旋重试,不阻塞等待锁的线程会进入阻塞状态,等待唤醒
适用场景低竞争高竞争

CAS 缺陷

ABA 问题

线程 1 把资源从 A 修改为 B,然后又修改为了 A;在线程 2 的角度来看,资源的状态依然是 A,以为没有被修改,实际上是修改后的状态恰好回到了最初状态

解决方式:使用 AtomicStampReference,每次修改都要加入版本号校验,保证完全没有被修改过。ABA 问题本质上是状态的丢失,通过引入版本号解决,类似数据库的乐观锁

java
public class ABADemo {
    @Getter
    static class Account {
        private int balance;
        public Account(int balance) { this.balance = balance; }
        public void withdraw(int amount) { balance -= amount; }
        public void deposit(int amount) { balance += amount; }
    }

    public static void main(String[] args) throws InterruptedException {
        // 1. 有问题的 CAS
        Account account = new Account(100);

        // 线程A:挂起前读取余额=100
        int expected = account.getBalance();

        // 线程B:修改余额
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            account.withdraw(50); // 100→50
            account.deposit(50);  // 50→100
        });
        thread1.start();

        thread1.join();

        // 线程A:恢复后 CAS
        if (account.getBalance() == expected) { // true! 但状态已变化
            account.withdraw(50); // 错误扣除!
        }
        System.out.println("错误结果: " + account.getBalance()); // 50 (应为100)

        // 2. 使用 AtomicStampedReference 解决 ABA 问题
        AtomicStampedReference<Account> safeAccount =
                new AtomicStampedReference<>(new Account(100), 0);

        int[] stampHolder = new int[1];
        Account expectedAcc = safeAccount.get(stampHolder);
        int stamp = stampHolder[0];

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            // 模拟状态变化
            Account newAcc = new Account(50);
            safeAccount.compareAndSet(expectedAcc, newAcc, stamp, stamp + 1);
            safeAccount.compareAndSet(newAcc, new Account(100), stamp + 1, stamp + 2);
        });
        thread2.start();

        thread2.join();

        // 线程A:CAS 检查版本号
        Account newAcc = new Account(50);
        boolean success = safeAccount.compareAndSet(
                expectedAcc, newAcc, stamp, stamp+1
        );
        System.out.println("修复结果: " + success); // false! 版本号已变
    }
}

自旋循环开销较大

AtomicInteger 原子递增操作来说,高竞争情况下,性能较差,因为可能要经历大量失败重试

解决方式:对于这种场景,使用 LongAddr 来代替 AtomicInteger 来实现累加,通过分段锁的方式来避免大量的自旋。对于其他非累加的场景,如果竞争同样很激烈,每次 CAS 的自旋开销总是大于 1 次,应该重新考虑是否需要是否 synchronized 来控制

java
public class AccumulationTest {
    public static void main(String[] args) throws InterruptedException {
        int threads = 100;
        int iterations = 10000;

        StopWatch stopWatch = new StopWatch();
        // 测试 AtomicInteger 自增性能
        // 使用 CAS 保证原子性,但在高并发下存在大量失败重试
        AtomicInteger atomicInteger = new AtomicInteger(0);
        ExecutorService executor = Executors.newFixedThreadPool(threads);

        stopWatch.start("AtomicInteger");
        for (int i = 0; i < threads; i++) {
            executor.submit(() -> {
                for (int j = 0; j < iterations; j++) {
                    atomicInteger.incrementAndGet();
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        stopWatch.stop();
        System.out.println("AtomicInteger: " + stopWatch.lastTaskInfo().getTimeMillis());

        // 测试 LongAdder 自增性能
        // 将累加操作分散到多个 Cell 中,最后再合并结果
        // 在高并发场景下性能优于 AtomicInteger
        LongAdder longAdder = new LongAdder();
        executor = Executors.newFixedThreadPool(threads);

        stopWatch.start("LongAdder");
        for (int i = 0; i < threads; i++) {
            executor.submit(() -> {
                for (int j = 0; j < iterations; j++) {
                    longAdder.increment();
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        stopWatch.stop();
        System.out.println("LongAdder: " + stopWatch.lastTaskInfo().getTimeMillis());
    }
}