CAS不依赖总线锁定,而是通过硬件原子指令(如x86的cmpxchg)配合MESI缓存一致性协议实现;lock前缀确保原子性与可见性,实际锁定缓存行而非总线。

CAS(Compare-And-Swap)在 Java 中由 Unsafe.compareAndSwapInt 等方法提供,但其底层**并不依赖总线锁定(Bus Locking)来实现**,而是主要依靠处理器提供的**原子指令(如 x86 的 cmpxchg)配合缓存一致性协议(如 MESI)**完成。总线锁定是早期或特殊场景下的备选机制,现代多核 CPU 几乎不使用它来实现 CAS。
为什么 CAS 通常不走总线锁?
总线锁定代价极高:它会强制挂起所有其他 CPU 核心对内存的访问,导致严重性能瓶颈和可扩展性问题。而现代处理器通过以下方式高效支持原子操作:
-
硬件原子指令:x86 提供
cmpxchg、ARM 提供ldrex/strex等指令,这些指令在 CPU 内部微架构层面保证单条执行的原子性; -
缓存行级锁定:当执行
cmpxchg时,CPU 会通过锁住目标地址所在缓存行(Cache Line)的方式实现原子读-改-写,而非锁整个总线; - MESI 协议保障可见性:修改缓存行时,其他核心的对应缓存副本会被置为无效,后续读取将触发重新加载,从而保证 CAS 比较时看到的是最新值。
什么情况下会退化到总线锁定?
仅在极少数不支持缓存一致性或未对齐访问的场景下,CPU 可能回退到总线锁(例如某些老式嵌入式平台),但在标准 x86-64 服务器/桌面 CPU 上:
- 对齐的 4 字节(
int)或 8 字节(long)内存地址,cmpxchg直接由缓存子系统处理; - Java 的
Unsafe调用最终编译为带lock前缀的指令(如lock cmpxchg),该前缀作用是**确保指令原子性并触发缓存一致性协议**,不是发起总线锁; - 即使加了
lock前缀,现代 CPU 仍优先使用缓存锁定(Cache Locking),只有在目标地址不在缓存中且无法被缓存(如映射到设备内存)时,才可能升级为总线锁——这在常规堆内存中不会发生。
JVM 和硬件的协作关键点
Java 层面的 CAS 能正确工作,依赖于 JVM 对硬件能力的适配:
- JVM 在启动时探测 CPU 支持的原子指令集(如是否支持
cmpxchg16b),并据此生成对应汇编; - HotSpot 使用
lock cmpxchg实现Unsafe::compareAndSwapInt,其中lock前缀确保该指令成为“原子操作序列”,同时刷新 store buffer 并同步 store forwarding; - Java 内存模型(JMM)规定 CAS 具有 volatile 语义:它既是原子写,也是原子读,并具备 happens-before 关系,这背后正是硬件缓存一致性和内存屏障(如
mfence)的支撑。
简单验证:看 JIT 编译后的汇编
启用 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 运行含 AtomicInteger.incrementAndGet() 的代码,可观察到类似如下汇编(x86-64):
这里的 lock 是指令前缀,作用是:
- 防止指令执行期间被中断或乱序;
- 使该操作对其他核心全局可见;
- 但实际锁定粒度是缓存行,不是前端总线。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











