cas不是锁的替代品,而是基于乐观并发控制的非阻塞同步机制,依赖硬件原子指令实现单变量原子更新,适合低竞争场景;复合操作或高竞争下仍需结合锁或其他高级同步工具协同使用。

Java 中不能“通过无锁 CAS 机制替代传统加锁操作”这种说法本身容易误解——CAS 不是加锁的“替代品”,而是另一类线程安全实现思路:它不靠阻塞和互斥,而是靠乐观重试和原子硬件指令来保障一致性。关键不是“替换锁”,而是“在合适场景下选择更轻量、非阻塞的方案”。
CAS 的本质是乐观并发控制
传统 synchronized 或 ReentrantLock 是悲观锁:假设冲突必然发生,先抢锁再干活,失败就挂起等待。而 CAS 是乐观锁:假设多数时候没冲突,直接尝试更新,只在检测到值被改过时才重试。
- 它依赖 CPU 提供的 cmpxchg 等原子指令,由硬件保证“比较+赋值”不可分割
- Java 里不直接调用底层指令,而是通过 java.util.concurrent.atomic 包封装好的原子类(如 AtomicInteger、AtomicReference)间接使用
- 所有原子类的 compareAndSet() 方法,就是公开暴露的 CAS 接口
典型场景:用 AtomicXXX 替代 synchronized 计数器
比如累加一个共享计数器,传统写法要加锁:
synchronized (this) {
count++;
}
用 CAS 可以写成:
atomicCount.incrementAndGet(); // 底层就是循环调用 compareAndSet
背后逻辑是:
- 读取当前值 A
- 计算新值 B = A + 1
- 调用 compareAndSet(A, B),成功则退出;失败说明期间被别人改了,重新读 A 再试
这个过程不阻塞线程,也不涉及锁对象监视器,自然避免了上下文切换和死锁风险。
不是所有地方都能直接换,得看操作粒度
CAS 天然适合单变量的原子更新,但遇到复合操作就力不从心:
- 想同时更新两个字段?CAS 一次只能保一个变量,需用 AtomicReference 包装整个对象,或引入版本号(如 AtomicStampedReference 解决 ABA)
- 想实现“读-改-写”逻辑(如先查余额再扣款),单纯 CAS 容易重试多次,甚至引发饥饿;此时可能仍需锁,或改用更高级结构如 StampedLock
- 高竞争下自旋会空耗 CPU,JDK 后续版本已在部分原子类中加入有限自旋+yield 优化,但无法根除
真正落地时,往往混合使用
Java 并发包的设计哲学不是“全锁 or 全 CAS”,而是分层协作:
- ConcurrentHashMap 内部用 CAS 更新桶头节点,但扩容时仍用 synchronized 控制迁移线程
- ReentrantLock 的非公平模式,在 tryAcquire 中也优先用 CAS 尝试获取 state,失败再入队等待
- 像 LongAdder 这类高性能计数器,更是 CAS + 分段 Cell + 最终合并,把竞争分散到多个变量上
所以,与其说“用 CAS 替代锁”,不如说“用 CAS 构建更细粒度、更低开销的同步原语”,让锁退居为兜底或协调角色。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











