Java中CAS通过JVM调用Unsafe方法映射为lock cmpxchg指令,lock前缀通过总线锁或缓存锁保障原子性;现代CPU优先使用缓存锁以避免总线锁开销;开发者需规避伪共享、慎用自旋并结合volatile使用。

Java 中的 CAS 操作在 x86 架构下,并不直接暴露 LOCK 前缀指令给 Java 程序员,而是由 JVM 通过 Unsafe.compareAndSwapXxx 方法调用底层 native 实现,最终映射为带 lock cmpxchg 的汇编指令。这个 lock 前缀才是触发内存总线(或缓存)锁定的关键机制。
LOCK 前缀如何保障原子性
lock 前缀的作用是让 CPU 在执行后续指令(如 cmpxchg)时,确保“读取旧值→比较→写入新值”这一整段操作不可被其他处理器打断。它不是锁住整个系统,而是针对该指令访问的**特定内存地址及其所在缓存行**施加独占控制:
- 若目标内存地址未缓存在本 CPU 中,或跨缓存行,则触发总线锁定(Bus Locking):CPU 拉低
LOCK#信号,暂时阻断其他 CPU 和 DMA 设备对内存总线的访问,直到指令完成 - 若目标地址已缓存在本 CPU 的 write-back cache 中,且处于 MESI 协议下的 Exclusive(E)或 Modified(M)状态,则仅执行缓存锁定(Cache Locking):不锁总线,而是通过缓存一致性协议使该缓存行变为独占,再在本地 cache 完成原子更新
为什么总线锁定开销大,而现代 CPU 很少用它
总线锁定会强制暂停所有其他核心对内存的读写,哪怕它们操作的是完全无关的地址——这严重限制了多核并行效率。因此,从 Pentium Pro(P6 微架构)开始,Intel 引入缓存锁定优化:
- 只要目标地址落在已缓存的、对齐的单个缓存行内(通常 64 字节),且该行处于 E/M 状态,就跳过总线锁
- JVM 和 HotSpot 在生成 CAS 相关代码时,会尽量保证对象字段内存对齐(例如使用
@Contended或填充字段),提升缓存锁定命中率 - 实践中,99% 以上的 CAS 操作在现代 CPU 上走的是缓存锁定路径,真正触发总线锁的情况极少(如访问未缓存的 MMIO 地址或非对齐内存)
CAS 对 Java 开发者的实际影响
虽然开发者看不到 lock cmpxchg,但理解其底层行为有助于写出更高效的并发代码:
立即学习“Java免费学习笔记(深入)”;
- 避免伪共享(False Sharing):多个线程频繁 CAS 修改位于同一缓存行的不同变量,会导致该缓存行在 CPU 间反复失效,性能陡降;应主动用 padding 隔开热点变量
-
慎用自旋重试:CAS 失败后通常循环重试(如
AtomicInteger.incrementAndGet()),若竞争激烈,可能造成大量无意义的lock cmpxchg尝试,浪费 CPU 资源 - volatile 与 CAS 协同工作:volatile 保证可见性(强制从主存/最新缓存读),CAS 保证原子性;二者常配合使用(如 AQS 中 state 字段)


















