饿汉式单例在类加载时初始化,线程安全但不支持延迟加载;双重检查锁懒汉式支持延迟加载且线程安全,但必须用volatile防止重排序;枚举单例最可靠,由JVM保障安全性。

饿汉式单例:类加载时就初始化,简单但不支持延迟加载
饿汉式本质是靠 static 字段 + 私有构造 + 公共静态访问器实现的线程安全单例。JVM 类加载阶段就完成实例化,天然避免多线程竞争问题。
常见错误是把 INSTANCE 声明成非 final,导致后续被反射或序列化破坏单例性;或者忘了加 private 构造函数,外部仍可 new 实例。
-
INSTANCE必须用public static final修饰,且初始化语句写在声明处(不能放到静态块里再赋值) - 构造函数必须是
private,否则无法阻止外部调用new - 如果类依赖外部资源(比如数据库连接),饿汉式会在应用启动时就触发初始化,可能拖慢启动速度或引发早期失败
示例:
public class EagerSingleton {
public static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
}双重检查锁懒汉式:延迟加载 + 线程安全,但 volatile 关键字不能少
懒汉式核心是“第一次调用 getInstance() 时才创建实例”,但直接加 synchronized 在方法上会严重拖慢性能。双重检查锁(DCL)是折中方案——外层判空不加锁,内层判空加锁,再加 volatile 防止指令重排序。
立即学习“Java免费学习笔记(深入)”;
最常踩的坑就是漏掉 volatile。没有它,JVM 可能将对象引用赋值提前到构造函数执行完之前,导致其他线程拿到一个未初始化完成的对象,进而抛出 NullPointerException 或更隐蔽的状态异常。
-
instance字段必须用volatile修饰,这是 DCL 正确性的前提 - 两次
if (instance == null)缺一不可:第一次减少锁开销,第二次防止多个线程同时通过第一层判断后重复创建 - 锁对象必须是
Singleton.class或当前类的静态 final 对象,不能用this或实例变量
示例:
public class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}为什么不用枚举实现单例?它其实更可靠
枚举单例是 Effective Java 推荐的方式,由 JVM 保证线程安全、反序列化安全、反射攻击防护。它不是“写法技巧”,而是语言机制级保障。
很多人回避枚举,是因为误以为它“不够面向对象”或“不方便继承”。但单例本就不该被继承,且枚举可以定义方法、实现接口,功能并不受限。
- 枚举实例天然
static final,无法通过反射调用私有构造(AccessibleObject.setAccessible(true)对枚举构造无效) - 反序列化时,JVM 总是返回已有枚举实例,不会新建
- 如果单例需要实现某个接口(如
Runnable),枚举可以直接implements
示例:
public enum EnumSingleton {
INSTANCE;
public void doSomething() { /* ... */ }
}单例测试和破坏场景:别只盯着写法,要验证是否真唯一
写完单例,不验证等于没写。尤其在多线程环境或涉及序列化/反射的场景下,看似正确的代码可能一跑就破。
容易被忽略的是:单元测试本身是单线程的,@Test 方法无法暴露 DCL 缺 volatile 的问题;而生产环境中的反射调用、反序列化、类加载器隔离等,都可能绕过你的保护逻辑。
- 用
Thread启动 100+ 线程并发调用getInstance(),断言所有返回引用相等(==) - 用
ObjectOutputStream/ObjectInputStream序列化再反序列化,检查是否仍是同一实例 - 用反射尝试调用私有构造函数,确认抛出
IllegalAccessException或构造未被执行
复杂点在于:这些破坏手段在不同 JDK 版本、不同 JVM 参数(如开启 JIT 优化)下行为可能不同。越想“绝对安全”,越得在真实运行环境中验证,而不是只看代码长得像单例。










