跳转至

volatile 的底层实现:lock 指令和内存屏障

一、lock 指令

volatile 写操作,JVM 会生成这样的字节码:

mov [addr], 0x1
lock add [rsp], 0   ; 一个空操作,带 lock 前缀

lock 前缀的作用:

  1. 锁缓存行(不是总线),用 MESI 协议让其他核缓存行失效。
  2. 把当前核缓存写回内存(或写无效化请求到其他核)。
  3. 禁止指令重排:lock 前的操作不能排到后面。

二、内存屏障(Memory Barrier)

JMM 定义了四种屏障:

屏障 作用
LoadLoad 读 1 → LoadLoad → 读 2:保证读 1 先于读 2
StoreStore 写 1 → StoreStore → 写 2:保证写 1 先于写 2
LoadStore 读 1 → LoadStore → 写 2:保证读 1 先于写 2
StoreLoad 写 1 → StoreLoad → 读 2:最强,写后读要等写刷出去

三、volatile 插入的屏障

写 volatile

普通写
StoreStore 屏障   ← 禁止前面的写重排到 volatile 写后
volatile 写
StoreLoad 屏障    ← 禁止 volatile 写重排到后面的读/写

读 volatile

volatile 读
LoadLoad 屏障     ← 禁止后面的读重排到 volatile 读前
LoadStore 屏障    ← 禁止后面的写重排到 volatile 读前
普通读/写

四、为什么需要 StoreLoad

StoreLoad 最贵,它要保证:写 volatile 后,后面的读必须看到最新值。

x86 上,lock 指令本身就实现了 StoreLoad 的语义。其他架构(ARM、PowerPC)需要单独的屏障指令。

五、和 MESI 的关系

  • lock 指令触发 MESI:把缓存行设为 M 并写回,其他核变 I。
  • 屏障禁止 CPU 和编译器的重排。

两者配合,volatile 的可见性和有序性才有硬件基础。

面试加分

  • x86 是强内存模型,只有 StoreLoad 需要显式屏障,其他屏障 x86 自动满足。
  • ARM 是弱内存模型,所有屏障都需要显式指令。