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

Java 中用 volatile 配合双重检查锁定(DCL)实现线程安全的单例,核心在于解决两个问题:防止多线程重复创建实例、避免读取到未完全初始化的对象。
volatile 关键字的两个关键作用
它不是简单“让变量可见”,而是精准干预 JVM 和 CPU 的执行行为:
-
禁止指令重排序:对象创建实际分三步——分配内存、初始化对象、将引用赋值给静态变量。没有
volatile时,JVM 或 CPU 可能把“赋值引用”提前到“初始化完成”之前。这时另一个线程看到引用不为null,就直接返回,结果拿到一个半初始化的对象,运行时报NullPointerException或状态错乱。 -
保证内存可见性:一个线程写入
instance = new Singleton()后,volatile强制把该值立即刷入主内存,并使其他线程本地缓存中这个变量失效。这样后续线程的第一次检查就能看到最新值,不会因缓存旧值而误判为null再次加锁。
双重检查锁定的两次判断逻辑
两次 if (instance == null) 不是冗余,各自承担不同职责:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 第一次检查(外层):在无锁状态下快速判断。绝大多数调用发生在单例已创建后,这里直接返回,完全不进同步块,避免锁开销。
-
第二次检查(内层):进入
synchronized块后再次确认。这是为了解决“多个线程同时通过第一次检查,排队等待锁”的场景——只有第一个获得锁的线程创建实例,其余线程拿到锁后发现实例已存在,直接返回,不重复创建。
标准写法与关键细节
必须同时满足以下条件,DCL 才真正安全:
- 静态实例字段声明为
private static volatile Singleton instance;——volatile缺一不可; - 构造函数私有:
private Singleton() {},防止外部 new; - 同步块使用类对象作为锁:
synchronized(Singleton.class),确保全局唯一锁; - 不能省略任意一次
null判断,也不能把volatile换成final或去掉。
为什么不用其他方案?
对比常见替代方式,DCL 在懒加载和性能间取得平衡:
- 饿汉式(static final):类加载时就初始化,线程安全但不懒加载,可能浪费资源;
- 同步整个方法:
public static synchronized Singleton getInstance():安全但每次调用都阻塞,性能差; - 只同步创建部分(无二次检查):仍存在竞态条件,多个线程可能同时创建实例;
- 枚举单例:确实更简洁安全,但无法延迟初始化(类加载即创建),且不支持带参数构造或继承扩展。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










