volatile在双重检查锁定单例中禁止指令重排序,确保其他线程看到完全初始化的实例;不加volatile可能导致“半初始化”对象被访问,引发空指针或逻辑错误。

volatile 在双重检查锁定(Double-Checked Locking)单例中,核心作用是**禁止指令重排序**,确保其他线程能安全看到完全初始化的实例。
为什么需要 volatile?
不加 volatile 时,JVM 可能将单例对象的构造过程重排序为:分配内存 → 设置引用 → 初始化对象。这样其他线程可能拿到一个尚未完成初始化的对象(即“半初始化”状态),导致空指针或逻辑错误。
volatile 通过写屏障(store barrier)和读屏障(load barrier),强制保证:
– new 实例的初始化操作在引用赋值前完成;
– 其他线程读取 instance 字段时,能看到其全部初始化结果。
标准写法(推荐)
以下是线程安全、高效且符合 Java 内存模型的实现:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查(加锁后)
instance = new Singleton(); // volatile 保证可见性与有序性
}
}
}
return instance;
}
}
关键细节说明
- volatile 必须修饰 instance 字段:仅加在引用上,不能加在构造方法或 getInstance 上
- 两次 null 检查缺一不可:第一次避免多数情况下的同步开销;第二次防止多个线程同时通过第一层检查后重复创建
- synchronized 锁的是类对象:保证全局唯一入口,也可用 private static final Object LOCK = new Object() 替代,但语义更清晰的是类锁
- 构造函数必须私有:防止外部通过 new 调用绕过控制
替代方案对比
如果对性能要求不高,可考虑更简洁安全的方式:
- 静态内部类:利用类加载机制保证线程安全和懒加载,无需 volatile 和 synchronized
- 枚举单例:天然防反射、反序列化攻击,最简最安全,但无法延迟初始化(类加载即创建)
双重检查锁定 + volatile 是平衡性能、懒加载与安全性的经典选择,适用于 JDK 1.5+ 环境。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










