DCL(Double-Check Locking 双重检查锁)单例的坑
一、核心结论
经典DCL最致命的是volatile缺失导致的半初始化对象空指针。
二、错误 vs 正确完整写法
❌ 错误写法(90%的人会写错,缺volatile)
java
public class Singleton {
// 致命错误:缺少volatile关键字
private static Singleton instance;
// 私有构造方法
private Singleton() {}
public static Singleton getInstance() {
// 第一次检查(无锁)
if (instance == null) {
// 加锁
synchronized (Singleton.class) {
// 第二次检查(有锁)
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
✅ 正确写法(加volatile禁止指令重排序)
java
public class Singleton {
// 关键:加上volatile关键字
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;
}
}
三、最致命坑:指令重排序
对象创建的两种执行顺序
┌───────────── 正常顺序(安全) ─────────────┐
│ 1. 分配对象内存空间 │
│ 2. 初始化对象(执行构造方法) │
│ 3. 将instance引用指向该内存地址 │
└───────────────────────────────────────────┘
┌───────────── 重排序后(危险) ─────────────┐
│ 1. 分配对象内存空间 │
│ 3. 将instance引用指向该内存地址 │
│ 2. 初始化对象(执行构造方法) │
└───────────────────────────────────────────┘
灾难发生完整流程
线程A执行 instance = new Singleton()
↓
JVM发生指令重排序:先执行步骤3,后执行步骤2
↓
instance ≠ null,但对象内部数据全是默认值(未初始化)
↓
线程B执行第一次检查:发现instance≠null,直接返回
↓
线程B使用这个"半成品"对象 → 抛出NullPointerException