volatile通过向jvm和编译器发出语义信号,促使javac和jit在volatile写前插入storestore屏障、读后插入loadload屏障,从而禁止跨屏障的编译器重排序,并在运行时生成cpu级内存屏障以同时约束编译器和处理器乱序。

volatile 怎么插内存屏障来拦编译器重排
volatile 关键字本身不直接插入内存屏障,而是向 JVM 和编译器发出明确语义信号:这个变量的读写必须严格按代码顺序执行,并对其他线程可见。编译器(如 javac)和 JIT 编译器在生成字节码或机器码时,会依据这个语义,在 volatile 变量的读/写操作前后自动插入特定类型的内存屏障指令。
编译器重排序的边界被屏障卡死
编译器优化重排序的前提是“不影响单线程语义”,而 volatile 的语义打破了这一前提——它引入了跨线程的执行约束。因此,编译器不能把普通语句随意挪到 volatile 操作的前后去。
- 在每个 volatile 写操作之前,插入 StoreStore 屏障:确保该写操作之前的全部普通写操作(如 a=1、b=2)都已完成并刷出,不会被重排到 volatile 写之后;
- 在每个 volatile 读操作之后,插入 LoadLoad 屏障:确保该读操作之后的全部普通读操作(如 c=x、d=y)不会被提前执行,必须等 volatile 读完成后再开始。
不是禁止所有重排,而是划定不可逾越的顺序线
内存屏障不阻止编译器在屏障内部做优化,只强制规定屏障两侧的操作不能跨过它重排。例如:
源码顺序:a = 1; // 普通写 → flag = true; // volatile 写 → log(); // 普通调用
编译器可以重排 a=1 和 log() 吗?不可以——因为 StoreStore 屏障锁住了 a=1 必须在 flag=true 之前完成,而 LoadStore 或 StoreLoad 类屏障(配合后续读)又会进一步约束 log() 不能越过 flag 读操作提前执行。最终效果是:a=1 一定在 flag=true 之前,flag=true 一定在后续依赖它的读之前。
注意:javac 很少重排,真正发力的是 JIT
实际中,javac 编译阶段对简单赋值的重排序非常保守,多数重排发生在运行时 JIT 编译阶段。volatile 的语义会持续传递给 JIT,使其在生成热点代码的机器指令时,插入 CPU 级别的内存屏障(如 x86 的 lock addl $0,0(%rsp) 或 ARM 的 dmb),从而同时压制编译器和处理器层面的乱序。










