dcl必须用volatile,因其禁止指令重排序并保证内存可见性,防止其他线程获取未初始化完成的对象;synchronized锁的是singleton.class对象;两个if分别承担性能优化与线程安全双重作用。

为什么DCL必须用 volatile?
因为不加 volatile,instance = new Singleton() 可能被JVM重排序成「分配内存→赋值引用→调用构造函数」,导致其他线程拿到一个未初始化完成的对象。这种对象字段为默认值(如 null、0、false),一访问就 NullPointerException 或逻辑错乱。
-
volatile禁止指令重排,保证「构造函数执行完」才允许其他线程看到非空的instance - 它还强制写操作对所有线程立即可见,解决内存可见性问题
- Java 5 起,
volatile语义被 JMM 严格定义,DCL 才真正可靠——但前提是它必须存在
synchronized(Singleton.class) 锁的是什么?
锁的是 Singleton.class 这个 Class 对象本身,不是某个实例,也不是变量名。它是 JVM 在类加载时创建的唯一对象,所有线程访问同一个 Singleton.class 引用,才能互斥进入临界区。
- 不能换成
this或任意新new Object(),否则锁失效(各线程锁不同对象) - 也不能用非
static字段,因为静态方法getInstance()没有this - 常见错误:写成
synchronized(instance)—— 此时instance还是null,直接抛NullPointerException
DCL 的两个 if (instance == null) 各管什么?
外层 if 是性能守门员,内层 if 是安全守门员。
- 外层:避免绝大多数调用进锁。实例已存在时,直接返回,零同步开销
- 内层:防止多个线程在第一次检查后都卡在
synchronized外排队,等前一个释放锁后,全部冲进来重复创建 - 没有外层 → 每次调用都抢锁,退化成同步方法;没有内层 → 高并发下仍可能创建多个实例
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 防反射攻击可加:if (instance != null) throw new RuntimeException();
}
public static Singleton getInstance() {
if (instance == null) { // ← 外层检查
synchronized (Singleton.class) {
if (instance == null) { // ← 内层检查
instance = new Singleton(); // ← 必须 volatile 保障安全
}
}
}
return instance;
}
}
真正难的不是写出来,而是理解「为什么少一个 volatile 就可能在线上偶发崩溃」——这种 bug 不会本地复现,只在高并发+特定 CPU 缓存行为下爆发。别信“我测试过没问题”,要信 JMM 规范和 JDK 实现。
立即学习“Java免费学习笔记(深入)”;










