单例模式本身不解决并发问题,关键在于实现时确保多线程下仍严格维持唯一实例:首选静态内部类(JVM类加载机制保障线程安全且懒加载),次选双重检查锁(需volatile+两次判空),再选饿汉式(线程安全但非懒加载)。

Java 中用单例模式解决多线程并发安全问题,核心不是“用单例去解决并发问题”,而是在实现单例时,确保其本身在多线程环境下仍能严格维持“唯一实例”这一语义。一旦多个线程同时触发初始化,就可能创建出多个实例,破坏单例契约,进而引发共享状态错乱、资源竞争等并发问题。所以关键在于:选对线程安全的单例实现方式。
下面介绍几种主流、可靠、生产可用的方案,按推荐优先级排序:
静态内部类(推荐首选)
这是目前最简洁、高效、天然线程安全的懒加载单例写法,完全依赖 JVM 类加载机制保障安全性。
- JVM 保证一个类的
<clinit>(静态初始化块)只执行一次且串行化,而SingletonHolder类仅在getInstance()第一次被调用时才被主动引用,从而触发初始化。 - 不需要
synchronized、不依赖volatile,无性能损耗,也规避了指令重排序风险。 - 构造函数保持
private,防止反射绕过(虽不能完全杜绝,但已是合理兜底)。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}⚠️ 注意:
立即学习“Java免费学习笔记(深入)”;
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
-
SingletonHolder必须是private static class -
INSTANCE必须是final - 不要在
SingletonHolder中做耗时操作或可能抛异常的初始化(否则类初始化失败后,后续调用直接NoClassDefFoundError)
双重检查锁(DCL)——需谨慎使用
适用于需要更精细控制初始化逻辑(如带参数构造、依赖注入)的场景,但必须正确使用 volatile。
- 第一次判空避免无谓加锁;
- 进入同步块后再判空,防止重复创建;
-
volatile关键字确保instance的写操作对所有线程立即可见,并禁止 JVM 和 CPU 对new Singleton()的指令重排序(如“分配内存→设置引用→调用构造器”被重排为“分配内存→设置引用→构造器未完成”,导致其他线程拿到未初始化对象)。
public class Singleton {
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;
}
}✅ 正确写法缺一不可:volatile + 两次判空 + 同步块内创建。
❌ 常见错误:漏掉 volatile、只判一次空、锁对象用错(如 this)、把 synchronized 加在方法上(粗粒度,性能差)。
饿汉式(简单稳妥,但非懒加载)
在类加载时就完成实例创建,利用 JVM 类加载线程安全特性,天然无并发问题。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}✔️ 优点:绝对线程安全、写法极简、无同步开销
❌ 缺点:实例在类加载时即创建,无法延迟初始化;若构造开销大或依赖外部资源(如配置、数据库连接),可能造成启动慢或初始化失败风险。
其他不推荐或已淘汰的方式
-
普通懒汉式(无同步):
if (instance == null) instance = new Singleton();→ 多线程下必然产生多个实例。 -
方法级
synchronized:public static synchronized Singleton getInstance()→ 安全但每次调用都串行,性能差,没必要。 - 枚举单例:虽线程安全且防反射/反序列化攻击,但无法延迟加载,且扩展性差(不能继承、不能含复杂初始化逻辑),适合极简场景,但不作为通用解。
本质上,单例模式本身不“解决”并发问题,它只是一种需要并发保护的设计结构。真正起作用的是你选择的初始化机制是否与 JVM 内存模型、类加载规范、同步语义严格对齐。静态内部类方案正是这种对齐的典范——不靠代码加锁,靠平台机制兜底。

















