dcl双重校验锁单例需用volatile修饰实例变量,以禁止指令重排序并保证可见性:否则new singleton()的三步(分配内存、初始化、赋值引用)可能被重排,导致其他线程拿到未完全初始化的对象而引发异常。

Java 中用 volatile 实现双重检查锁定(Double-Checked Locking)单例,核心是解决指令重排序导致的“对象未完全初始化就被其他线程使用”的问题。关键在于:把单例实例声明为 volatile,强制禁止 JVM 对其构造过程的重排序,并保证可见性。
为什么需要 volatile?
不加 volatile 时,以下三步可能被重排序:
- 分配内存空间
- 调用构造方法初始化对象
- 将引用赋值给静态变量(如
instance = new Singleton();)
JVM 可能将第 2 步和第 3 步重排序——先让 instance 指向一块已分配但尚未初始化完成的内存。此时另一个线程进入第一次判空,发现 instance != null,直接返回这个“半初始化”对象,调用其方法就会抛出 NullPointerException 或行为异常。
volatile 能禁止这种重排序,并确保所有线程看到的都是完全初始化后的实例。
标准写法(推荐)
以下是线程安全、高效、符合 Java 内存模型的实现:
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修饰静态实例字段,仅加锁或仅判空都不够 - 构造方法保持私有,防止外部 new 实例
- 适用于 JDK 5 及以上版本(
volatile的语义在 JSR-133 后才明确支持 DCL) - 如果单例类需要传参或依赖注入,DCL 不再适用,建议改用静态内部类或
Enum方式
替代方案对比(简要)
虽然 DCL 是经典解法,但实际开发中更推荐:
- 静态内部类:利用类加载机制保证线程安全,无同步开销,延迟加载,代码更简洁
- 枚举单例:天然防反射、反序列化攻击,最简洁安全(《Effective Java》强烈推荐)
除非对性能极度敏感且明确需懒汉式 + 显式控制初始化时机,否则不必硬套 DCL。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











