lock cmpxchg 是 x86 平台上 cas 的原子指令,其中 cmpxchg 完成比较并交换,lock 前缀保障多核缓存一致性;它不可分割、失败返回旧值,且必须配合 volatile 保证可见性。

CAS 的底层汇编指令 lock cmpxchg 不是 Java 程序员直接写的代码,而是 JVM 在调用 Unsafe.compareAndSwapInt 等方法时,由 HotSpot 编译器在 x86_64 平台上自动生成的机器级指令。理解它,关键不是背指令格式,而是抓住“为什么加 lock”和“cmpxchg 干了什么”这两点。
lock cmpxchg 的三个核心作用
lock cmpxchg 是一条复合指令:cmpxchg 负责“比较并交换”,lock 前缀负责多核环境下的执行保障。它不是两步操作,而是一条原子指令:
- cmpxchg:把内存地址里的值读出来,跟寄存器中的“预期值”比较;相等就写入“新值”,不等就不写;整个过程不可拆分,硬件保证中间不被中断
- lock 前缀:告诉 CPU:“这条指令访问的内存地址,必须独占访问”。它不锁整个系统,也不锁整条总线,而是触发缓存一致性协议(如 MESI),确保其他 CPU 核心不能同时修改同一缓存行
-
失败返回机制:指令执行完,CPU 自动把内存原值放进
rax寄存器(x86 下),Java 层据此判断是否成功——成功返回 true,失败返回 false,并暴露旧值供上层重试
为什么不能只写 cmpxchg,非要加 lock?
单核时代,cmpxchg 本身就能保证原子性;但在多核环境下,没有 lock,两个核心可能同时读到同一个旧值、都判断为“相等”、都写入新值——结果变成覆盖式更新,CAS 就失效了。
lock 的实际效果取决于硬件状态:
- 如果目标变量已在本核 cache 中且处于 Exclusive 或 Modified 状态 → 直接本地完成,走缓存锁(Cache Locking),开销极小
- 如果该变量在其他核 cache 里是 Shared 状态 → 本核发起 RFO(Request For Ownership)请求,强制其他核失效对应缓存行 → 仍是缓存行级控制,不锁总线
- 只有在极特殊场景(比如访问 uncached 内存、跨缓存行对齐)→ 才退化为总线锁(Bus Locking),现代 CPU 几乎不会发生
Java 层怎么“看到”这条指令?
你写 atomicInteger.compareAndSet(1, 2),JVM 实际做了这些事:
- 通过
Unsafe调用 JVM 内建的原子函数(如Atomic::cmpxchg) - JIT 编译器识别该调用,在生成本地代码时插入
lock cmpxchg指令(x86 下)或对应平台指令(ARM 是ldaxr/stlxr) - 指令执行后,JVM 把 CPU 返回的状态(ZF 标志位 + rax 中的旧值)转成 Java 的 boolean 和语义逻辑
你不需要手写汇编,但可以通过 -XX:+PrintAssembly 参数让 JVM 输出热点方法的汇编,搜 lock cmpxchg 就能看到它真实出现在哪一行。
真正影响性能的关键点不在指令本身
懂了 lock cmpxchg 是什么,下一步要关注它在实际并发中怎么“出问题”:
-
伪共享(False Sharing):多个原子变量挨得太近,落在同一缓存行里,一个被改,整行失效,导致其他核频繁同步 → 解决办法是用
@Contended或手动 padding -
自旋空转:CAS 失败后循环重试,若竞争激烈,会浪费大量 CPU 时间 → 应结合退避策略(如
Thread.onSpinWait())或换用锁 -
ABA 问题:值从 A→B→A,CAS 误判为“没变” → 需用
AtomicStampedReference加版本戳 -
volatile 必须配合:CAS 变量必须声明为
volatile,否则其他线程看不到最新值,CAS 会基于过期值重试 → 这不是 lock 指令的事,是内存可见性问题
指令本身很稳定,真正决定 CAS 是否高效、是否安全的,是你的数据布局、重试逻辑和内存模型理解。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











