看似完美的单例模式,实际上隐藏着致命的指令重排序问题!volatile是救命稻草。
一、经典DCL单例模式
错误的实现❌
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // ① 第一次检查
synchronized (Singleton.class) { // ② 加锁
if (instance == null) { // ③ 第二次检查
instance = new Singleton(); // ④ 创建对象 💣
}
}
}
return instance;
}
}
二、问题:指令重排序
字节码分析
instance = new Singleton();
实际执行三步:
- 分配内存空间
- 初始化对象
- instance指向内存
可能重排序为:
- 分配内存
- instance指向内存(此时对象未初始化!)
- 初始化对象
时序问题
时刻1: 线程A执行到④,重排序后先让instance!=null
时刻2: 线程B执行①,发现instance!=null
时刻3: 线程B返回instance(但对象还未初始化!)💥
时刻4: 线程B使用instance,空指针异常或数据错误
三、正确的解决方案✅
方案1:volatile(推荐)
public class Singleton {
private static volatile Singleton instance; // volatile!
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile作用:
- 禁止指令重排序
- 保证可见性
方案2:静态内部类(最优雅)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
原理: 类加载机制保证线程安全。
方案3:枚举(最安全)
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
// 使用
Singleton.INSTANCE.doSomething();
优势:
- 天然单例
- 防止反序列化
- 防止反射攻击
关键总结:
- DCL必须用volatile
- 或用静态内部类
- 生产推荐枚举单例
下一篇→ Exchanger的实现原理🔄