Skip to content

概念卡片:CAS 乐观锁与原子类

一句话机制

CAS(Compare And Swap,比较并交换)是乐观锁思想:更新前先判断变量是否还是自己当初看到的值,是就更新、不是就失败重试;底层靠 CPU 原子指令 lock cmpxchg 保证「比较+交换」不可分割。 它是 AtomicIntegerConcurrentHashMap、AQS 等一切 JUC 无锁并发的基石。

核心思想

CAS(V, A, B):当内存值 V == 期望旧值 A 时,把 V 更新为 B,否则失败

count++ 不是原子操作(读→加1→写回),两线程同时自增会丢更新。CAS 不阻塞线程,而是失败就重试——这就是「CAS + 自旋」。

java
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next)) return next;
    }
}

底层实现

  • Java 层:sun.misc.Unsafe.compareAndSwapInt(...)
  • CPU 层:x86 的 lock cmpxchglock 前缀在多核下锁总线,保证指令执行期间其他核不能访问该内存地址)

volatile 与 CAS 的分工volatile 保证「看得见最新值」,CAS 保证「比较并更新这个动作是原子的」。AtomicInteger 内部是 private volatile int value + CAS 更新。

CAS 三大经典问题

问题现象解法
ABA 问题T2 把 A→B→A,T1 的 CAS 误以为没变过AtomicStampedReference(值+版本号一起比)
自旋开销高竞争下大量线程 CAS 失败空转耗 CPULongAdder(分散热点)、分段、必要时用锁
单变量局限只能保证一个变量的原子性封装不可变对象 + AtomicReference 整体替换,或直接加锁

ABA 详解

ABA:值从 A 改成 B 又改回 A,CAS 只比较值,发现还是 A 就更新成功,但中间已经变过两次。解决靠版本号A(1) → B(2) → A(3),值相同但版本号不同,AtomicStampedReference 同时比较值和版本号。

LongAdder:高并发计数优化

AtomicLong 所有线程竞争同一个 value,并发越高 CAS 失败越多。LongAdderbase + Cell[] 分散热点:竞争不激烈更新 base,激烈时分散到不同 Cell,求和时 sum = base + Σcell

  • 低并发 + 需准确即时值AtomicLong
  • 高并发 + 统计计数(访问量/指标)→ LongAddersum() 非强一致瞬时值)

CAS vs 锁的选型

维度CAS(乐观锁)synchronized / ReentrantLock
适合操作简单、变量少、冲突不严重、想免阻塞临界区复杂、多变量、竞争激烈、需等待唤醒
场景自增计数、状态 0→1、引用替换、简单累加转账、库存扣减+日志、多字段更新、生产者消费者

常见误解(避坑)

  1. ❌ "AtomicReference 比较的是对象内容"。→ 比较的是引用是否相同==),不是 equals;对象内容变了但引用没变,CAS 感知不到。
  2. ❌ "CAS 无锁就永远比锁快"。→ 竞争激烈时自旋空转耗 CPU,性能可能反而不如阻塞锁;「无锁」不是银弹,要按场景选。
  3. ❌ "volatile 能替代 CAS"。→ 两者各管一半:volatile 管可见性、CAS 管原子更新,count++ 光有 volatile 仍会丢更新。
  4. ❌ "LongAdder.sum() 是精确瞬时值"。→ 它是各 Cell 的弱一致求和,适合统计场景,不适合需要精确值的业务判断。

关联

最近更新