CAS是CPU级原子指令(如x86的cmpxchg),非Java代码组合,依赖硬件保障“比较+更新”不可分割;它不阻塞、失败返回false需自旋重试,是AtomicInteger等原子类的基石;存在ABA问题(用AtomicStampedReference解决)和长时自旋开销(可用Thread.onSpinWait或LongAdder优化)。

CAS(Compare and Swap)不是“加个锁就完事”的机制,它靠的是硬件指令级的原子性保障,复习时必须穿透到 CPU 指令、JVM 实现和业务陷阱三层。
理解 CAS 的底层执行逻辑
CAS 本质是一条 CPU 原子指令(x86 上是 cmpxchg),不是 Java 层面的代码组合。它在多核系统中会自动申请总线锁或使用缓存一致性协议(如 MESI)来保证“比较+更新”不可分割。Java 中的 Unsafe.compareAndSwapInt 等方法,最终就是调用这条指令——所以它快,也所以它不依赖操作系统调度。
关键要明白:
– 不是“先读再比再写”,而是一次性完成的硬件操作;
– 失败不阻塞,而是返回 false,由上层决定是否重试(即自旋);
– 所有基于 CAS 的原子类(如 AtomicInteger、AtomicReference)都建立在这个基石之上。
直击两个经典缺陷:ABA 与自旋开销
只懂“成功就改、失败就重试”远远不够,面试和线上问题常出在这两处:
-
ABA 问题:值从 A→B→A,CAS 误判为“未被修改”。这不是理论漏洞,真实出现在对象池、无锁栈等场景。解决不是靠“想当然”,而是用
AtomicStampedReference——它把版本号(stamp)和引用绑定在一起,compareAndSet(expectedRef, newRef, expectedStamp, newStamp)要求两者同时匹配才生效。 -
长时自旋开销:若竞争激烈(比如高并发抢同一计数器),线程可能在循环里空转几十甚至上百次,白白消耗 CPU。这不是“等一等就好”的问题,需结合业务权衡:可引入退避策略(如
Thread.onSpinWait())、降级为锁,或重构为分段计数(如LongAdder的 cell 分治设计)。
对照源码看 JDK 的实际运用
别停留在概念,打开 java.util.concurrent.atomic 包源码验证:
立即学习“Java免费学习笔记(深入)”;
-
AtomicInteger.getAndIncrement()底层是unsafe.getAndAddInt(this, valueOffset, 1),而后者就是一个典型的 do-while 自旋 CAS 循环; -
ConcurrentHashMap在 JDK 1.8 中插入新节点时,先用 CAS 尝试设置桶首节点,失败则锁住该桶头节点再处理,体现“CAS 优先 + 锁兜底”的混合策略; -
Unsafe类虽被标记为@Deprecated,但仍是VarHandle和jdk.internal.misc.Unsafe的事实基础,理解它才能看懂现代原子操作的演进路径。
动手验证比背诵更重要
光读不练等于没学。建议做三件事:
- 写一个极简的无锁计数器,用
AtomicInteger和普通int对比,在 10 个线程下各累加 10 万次,观察结果差异; - 手动构造 ABA 场景:用
AtomicReference存对象引用,让线程 A 取出后挂起,线程 B 修改两次再恢复原值,再看线程 A 的 CAS 是否意外成功; - 用 JMH 写基准测试,对比
AtomicInteger、LongAdder、synchronized在高争用下的吞吐量和延迟,数据比口头解释更有说服力。


















