volatile用于禁止指令重排序并保证可见性,防止线程读取到未完全初始化的单例对象;其通过写屏障确保“分配内存→初始化字段→赋值引用”顺序执行,使其他线程总能看到完全构造好的实例。

volatile 在双重检查锁定(Double-Checked Locking)中起关键作用:它禁止指令重排序,并确保对象引用的写入对其他线程立即可见,从而防止其他线程看到一个“已分配内存但构造函数尚未执行完毕”的半初始化对象。
为什么需要 volatile?——构造函数可能被重排序
在没有 volatile 修饰时,JVM 可能将以下三步操作重排序:
- 1. 分配对象内存(memory allocation)
- 2. 调用构造函数初始化字段(ctor execution)
- 3. 将对象引用赋值给静态变量(assign reference)
步骤 1 和 3 可能被提前合并(即先分配内存、立即写引用,再执行构造),导致其他线程在判断 instance != null 为真后,拿到一个字段还未初始化的对象。这就是“半初始化”问题。
volatile 如何阻止重排序?
volatile 写操作具有“**禁止指令重排序**”和“**写屏障(store barrier)**”语义:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 编译器和处理器不会把构造函数内的写操作重排到 volatile 写之后
- volatile 写之前的所有操作(包括对象字段初始化)必须在该写之前完成
- volatile 读具有“读屏障”,保证能看到之前所有已完成的写操作(含字段初始化)
这就强制了“分配 → 初始化 → 发布引用”的安全顺序,使其他线程看到的一定是完全初始化的对象。
正确写法示例
以下是线程安全的双重检查单例模式:
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;
}
}
注意:new Singleton() 不是原子操作,但 volatile 修饰的引用赋值,配合其内存屏障,保障了整个初始化过程对其他线程的正确可见性。
不加 volatile 的风险(JDK 5 之前尤其严重)
在 JDK 5 之前,即使使用 synchronized,也不能完全禁止重排序问题;JDK 5 引入了 JSR-133 内存模型,明确赋予 volatile 更强的语义——正是这个改进,让双重检查锁定真正变得可靠。不加 volatile,即使在现代 JVM 中,也属于未定义行为(undefined behavior),极难复现但真实存在。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










