volatile变量读取时jvm强制从主内存加载最新值,通过插入读屏障禁止重排序并清空本地缓存,借助硬件缓存一致性协议(如mesi)实现可见性,避免编译器优化导致的读取 stale 值。

Java 中 volatile 变量在读取时,JVM 会强制从主内存(main memory)重新加载值,而不是使用线程本地的工作内存(如 CPU 缓存或寄存器中的副本),从而保证看到的是其他线程写入的最新值。
volatile 读操作的内存语义
当一个线程读取 volatile 变量时,JVM 会在底层插入一个“读屏障”(Load Barrier),它有两个关键作用:
- 禁止该读操作与之前的读/写操作重排序(即不允许把 volatile 读“提前”)
- 清空当前线程工作内存中该变量的缓存副本,直接向主内存发起读请求
注意:这里的“主内存”是 JVM 规范中的抽象概念,实际对应硬件层面的主存(RAM),但具体是否真的绕过 CPU 缓存,取决于底层内存模型(如 x86 的 MESI 协议)和 JIT 编译器优化。JVM 通过内存屏障指令(如 lock addl $0x0, (%rsp) 或 mfence)确保可见性。
为什么不是每次读都“真正访问 RAM”?
现代 CPU 不会每次都真正走总线去读主存——那样太慢。volatile 的效果本质是借助硬件缓存一致性协议(如 Intel 的 MESI)来实现:
- 当一个线程写 volatile 变量,会触发缓存行失效(invalidate)广播
- 其他线程下次读该变量时,若缓存行已失效,CPU 自动从主存(或其它核的缓存)同步最新值
- JVM 的读屏障确保这个同步行为对 Java 程序可见,且不会被编译器优化掉
和普通变量读取的关键区别
普通变量读取可能长期停留在线程的工作内存中,JIT 甚至可能将其优化为寄存器常量(比如循环里反复读一个非 volatile 的 flag);而 volatile 读:
- 禁止编译器将该读操作提升出循环(no loop hoisting)
- 禁止将其缓存到局部变量或寄存器中复用
- 每次执行字节码
getfield或getstatic时,都必须重新加载
一个典型例子
比如用 volatile 实现简单的线程间通知:
volatile boolean ready = false;
// 线程 A
ready = true; // 写 volatile → 刷新到主内存 + 发送失效信号
// 线程 B
while (!ready) { // 每次读都重新加载,不会卡在旧值上
Thread.yield();
}
如果没有 volatile,线程 B 可能永远看不到 ready = true,因为它的读操作被优化或一直命中本地缓存。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











