DCL+volatile是Java懒汉式线程安全单例最成熟方案,因volatile禁止重排序并保证可见性,双重判空分别避免无谓加锁和多线程重复初始化。

直接用双重检查锁定(DCL)+ volatile 是目前 Java 中实现懒汉式线程安全单例最成熟、兼顾性能与正确性的方案。关键不在“用了没”,而在“为什么必须这么写”——漏掉 volatile 或顺序错乱,就可能让其他线程拿到半初始化的对象。
volatile 是内存屏障,不是可有可无的修饰符
没有 volatile,JVM 可能将 new Singleton() 拆成三步:
① 分配内存空间
② 初始化对象字段
③ 将 instance 引用指向该内存地址
而步骤②和③可能被重排序。结果就是:另一个线程看到 instance != null,但实际字段还是默认值(如 int=0、Object=null),一调用就空指针或逻辑错乱。
volatile 强制禁止这种重排序,并保证写操作对所有线程立即可见。
双重检查的两次判空缺一不可
第一次判空在 synchronized 外:避免已初始化后每次调用都抢锁,提升高并发下的吞吐量。
第二次判空在 synchronized 内:防止多个线程同时通过第一次检查后,争抢进入同步块,最终只允许一个线程真正构造实例。
两者配合,实现了“只在首次创建时加锁,后续完全无锁访问”的目标。
标准写法模板(Java)
以下是最小、最稳妥的实现,适用于 JDK 1.5+:
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();
}
}
}
return instance;
}
容易踩的坑
- 把 volatile 写在方法参数或局部变量上——无效,必须修饰静态字段
- 用 synchronized(this) 替代 synchronized(Singleton.class)——this 还没造出来,没法用
- 在构造函数里泄露 this(比如启动线程、注册监听器)——即使加了 volatile,也可能让其他线程拿到未构造完的对象
- 用普通 boolean 标志位替代 volatile instance 判空——无法保证可见性,等同于没锁










