volatile通过插入内存屏障禁止指令重排序并保证内存可见性,确保写操作前的指令不被排到写后、读操作后的指令不被排到读前,从而在双检锁单例等场景中防止未初始化对象被访问。

volatile 关键字在 Java 中主要通过禁止指令重排序和保证内存可见性,来解决多线程下因编译器或 CPU 优化导致的逻辑错误。它不加锁、不开销大,但必须用在恰当的位置才能真正起效。
volatile 如何禁止指令重排序
Java 编译器和处理器为提升性能,可能对没有数据依赖的语句进行重排。比如对象构造中“分配内存→初始化字段→设置引用”三步,若被重排为“分配内存→设置引用→初始化字段”,其他线程就可能拿到一个未完全初始化的对象。
volatile 修饰变量后,会在其读写操作前后插入内存屏障(Memory Barrier),强制约束指令顺序:
- 写 volatile 变量前的所有操作,不能被重排到写之后
- 读 volatile 变量后的所有操作,不能被重排到读之前
- 读写之间形成“happens-before”关系,确保操作顺序可预期
典型场景:双检锁单例中的 volatile
这是最常被引用的实战案例。若不加 volatile,instance = new Singleton() 这一行可能被重排,导致其他线程看到非 null 的 instance 引用,但对象内部字段尚未初始化。
立即学习“Java免费学习笔记(深入)”;
正确写法如下:
public class Singleton {
private static volatile Singleton instance; // 必须加 volatile
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // volatile 阻止此处重排
}
}
}
return instance;
}
}
关键点在于:volatile 保证了 instance 赋值操作的原子可见性与顺序性,使“引用赋值”严格发生在“对象完全初始化”之后。
其他适用场景与注意事项
除了单例,volatile 也适用于状态标志、一次性初始化、轻量级通知等场景:
- 用作运行控制标志(如
volatile boolean running),避免线程因缓存旧值而无法退出循环 - 配合其他同步机制使用,例如作为“初始化完成”的信号量,供后续读线程判断是否可安全访问共享资源
- 不能用于复合操作(如
count++),因为 volatile 不保证原子性;这类操作仍需 synchronized 或 AtomicInteger - 仅适用于“一写多读”模型;若存在多个写线程竞争修改同一 volatile 变量,仍需额外同步
验证是否生效的小技巧
可通过去掉 volatile 后观察行为变化来理解其作用:
- 在测试代码中将
volatile boolean ready改为普通 boolean,常会出现死循环或读到 0 值 - JIT 编译器可能对无 volatile 的变量做激进优化(如把 while 循环判定内联为常量),加上 volatile 后会强制每次读主内存
- 注意:JVM 规范不保证无 volatile 时一定出错,只是“可能出错”,所以不能靠测试“没出错”就认为可以省略


















