dcl单例必须用private static volatile声明实例,配合两次判空与synchronized块;缺volatile会导致半初始化对象,构造器需私有且无副作用,替代方案有静态内部类、枚举和饿汉式。

在单例模式中,用双重检查锁定(DCL)安全定义实例变量,关键不是“怎么声明”,而是“怎么声明 + 怎么用”——三者缺一不可:私有静态、volatile 修饰、配合两次判空与 synchronized 块。
必须用 private static volatile 声明
实例变量要写成这样:
private static volatile Singleton instance;
不能省略 volatile,这不是性能优化选项,而是线程安全的底线。原因在于:
• JVM 可能将 new Singleton() 拆为三步:分配内存 → 赋值引用 → 初始化对象;
• 若无 volatile,第二步和第三步可能被重排序,导致其他线程看到非 null 的 instance,但其字段仍是默认值(如 int=0、String=null);
• volatile 插入内存屏障,强制保证“构造完成才赋值”,同时让所有线程读到最新值。
常见错误写法:
-
private static Singleton instance;—— 缺少 volatile,高概率出半初始化对象 -
private static final Singleton instance = new Singleton();—— 这是饿汉式,不是 DCL,不满足懒加载 -
private volatile Singleton instance;—— 缺少 static,每个实例都有一份,彻底失去单例意义
两次判空不能少,顺序不能错
getInstance() 方法里必须严格按这个结构写:
第一次判空在锁外,第二次在锁内:
- 第一次检查:避免已创建后还抢锁,提升并发读性能
- 第二次检查:防止多个线程排队进同步块后,都执行 new 实例
标准写法(不可颠倒、不可省略任一 if):
if (instance == null) {<br> synchronized (Singleton.class) {<br> if (instance == null) {<br> instance = new Singleton();<br> }<br> }<br>}
构造器私有 + 无其他初始化副作用
确保单例真正唯一,还需两个配套约束:
- 构造方法必须是
private Singleton() {},禁止外部 new 或反射绕过(反射需额外防御) - 构造函数体内不能有耗时操作、阻塞调用或依赖未就绪的全局状态,否则会拖慢首次获取,并可能引发超时或死锁
替代方案也值得考虑
DCL 正确实现虽可靠,但对 volatile 和同步逻辑要求严苛。若项目允许,可优先考虑更简洁安全的替代方式:
- 静态内部类:利用 JVM 类加载机制的天然线程安全,延迟加载且无锁
- 枚举单例:JVM 保证枚举实例的创建是原子且线程安全的,还能天然防反序列化攻击
- 饿汉式 + static final:适合启动即用、无初始化依赖的场景,代码最简,绝对安全
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











