Skip to content

什么是 Java 的 CAS(Compare-And-Swap)操作? ​

  • 标签:Java并发、Java

🏛 业务架构师 ​

CAS(Compare-And-Swap)是CPU 级原子指令:比较内存值与预期值,相等则更新为新值,否则失败重试。Java 封装为 Unsafe/VarHandle 的 compareAndSet,AtomicInteger、LongAdder、AQS 全基于它。

业务本质:乐观并发的硬件实现——不加锁,先干活,提交时验证"我读的值没被别人改过",冲突就重试。

什么时候 CAS 是对的(按冲突率定价):

  • 低冲突高并发读:统计计数、状态标记、缓存引用替换——AtomicReference 更新配置快照,读无锁全速、写偶发重试,成本结构最优;
  • 冲突激烈时 CAS 反而灾难:千线程竞争一个 AtomicInteger,大量自旋空转烧 CPU。热点计数用 LongAdder(分段累加,读时合并)——JDK 这个演进本身就是"冲突率高时分段"的教学案例,秒杀库存这类热点行也一样:分段库存或队列串行化。

ABA 问题必须主动防:值从 A 改 B 又改回 A,CAS 认为"没变过"但世界已经变了。业务例子:账户余额扣 100 又存 100,余额相同但交易历史已完全不同;链表场景指针被改回但指向的对象已换。解法:带版本号的 AtomicStampedReference。我的规约:凡是"状态语义"而不只是"数值语义"的并发更新,必须版本号化——订单状态机、审批流全适用。

架构总结:悲观锁(排队)与乐观锁(重试)的选择题,答案由冲突概率与重试成本决定;CAS 是后者的实现底座,用对场景它便宜高效,用错热点它就是 CPU 焚烧炉。

🏗 系统架构师 ​

CAS 是硬件级原子指令(x86 的 CMPXCHG、ARM 的 LL/SC)暴露给 JVM 的乐观并发原语:比较内存值 V 与预期值 E,相等则更新为新值 N,否则失败重试——读改写变成一条不可中断的原子指令,无需加锁。

Java 层形态:Unsafe.compareAndSwapXxx → Atomic 系列(AtomicInteger、AtomicReference)、ABA 用的 AtomicStampedReference、LongAdder(分段 Cell 降低竞争热点)、AQS 抢 state、ConcurrentHashMap 初始化/转树全靠它。

三大利弊(辩证讲):

  • 优势:无阻塞、无上下文切换,低竞争下性能碾压锁;
  • ABA 问题:值从 A→B→A,CAS 误判"没变过"——数值统计场景无害,链表/引用状态场景致命,解法加版本号(AtomicStampedReference);
  • 自旋开销:高竞争下失败重试空转烧 CPU,LongAdder 用分散热点替代死磕,或退回锁;
  • 只能保一个变量的原子性,多变量还得锁或封装成不可变对象。

架构视角:CAS 是"乐观并发"的硬件根基,与业务层的乐观锁(版本号更新)、分布式层的 Redis SETNX、数据库层的避免锁升级一脉相承——冲突率低用乐观、冲突率高用互斥,这条选型准则贯穿从 CPU 到分布式的每一层。

💻 全栈开发专家 ​

CAS = Compare-And-Swap:一条 CPU 原子指令(x86 的 lock cmpxchg),语义"内存值 V、预期值 E、新值 N,V==E 才把 V 改成 N,否则失败"。Java 通过 Unsafe.compareAndSwapInt 暴露,整个 JUC 无锁体系的地基。

java
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();   // 内部自旋 CAS:旧值→+1→compareAndSet失败重试

本质是乐观锁的硬件实现:不阻塞线程,冲突就重试(自旋)。无死锁、无挂起开销,竞争不激烈时远快于锁。

三个衍生问题(面试必被追问):

  1. ABA:值 A→B→A,CAS 误判"没人动过"。解法 AtomicStampedReference(带版本号)。资金/链表指针场景必须防,纯计数无所谓。
  2. 自旋开销:高冲突下 CAS 空转烧 CPU。解法:LongAdder(分段 Cell 分散热点,sum 时聚合——我改造过的高并发计数就是这么优化的)、退避、或干脆上锁。
  3. 单变量限制:一次只能保证一个变量的原子性,多变量要么锁、要么封装成一个对象用 AtomicReference。

我看过的一个源码细节:AtomicInteger.lazySet()(putOrderedInt)只保证最终可见,省屏障换性能,用于统计场景——能说出这类细节说明真用过,不是背概念。