双重检查锁定(DCL)配合 volatile 是兼顾性能与线程安全的单例实现,必须用 volatile 防止指令重排序导致“半初始化”;枚举单例最简且天然线程安全但不支持懒加载;静态内部类方式利用类加载机制实现懒加载与线程安全。

双重检查锁定(DCL)配合 volatile 是 Java 中最常用、兼顾性能与线程安全的单例实现方式,但必须正确使用,否则仍可能出错。
为什么懒汉式直接加 synchronized 方法不行
虽然 public static synchronized Singleton getInstance() 能保证线程安全,但每次调用都阻塞,包括已初始化后的读操作——严重拖慢高并发场景下的吞吐量。真正需要同步的只有首次初始化那一小段代码。
双重检查锁定(DCL)必须用 volatile 修饰实例变量
不加 volatile 会导致指令重排序:JVM 可能将对象内存分配、构造函数执行、引用赋值三个步骤重排,使其他线程看到一个未完全构造好的 Singleton 实例(即“半初始化”状态),进而引发 NullPointerException 或逻辑错误。
正确写法:
立即学习“Java免费学习笔记(深入)”;
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;
}}
枚举单例是更简洁且天然线程安全的选择
如果单例不需要延迟加载(即类加载时就初始化),或你希望彻底规避反射/反序列化攻击,enum 是最简、最安全的方案——JVM 保证枚举实例的创建是原子且线程安全的,且无法通过反射调用私有构造器生成新实例。
示例:
public enum Singleton {
INSTANCE;
public void doSomething() { /* ... */ }}
- 获取实例:
Singleton.INSTANCE - 不能被反射破坏(
Enum构造器被 JVM 特殊保护) - 自动支持序列化,无需实现
readResolve - 缺点:不支持懒加载(
INSTANCE在类首次主动使用时即初始化)
静态内部类方式适合需要懒加载又不想用 volatile 的场景
利用 Java 类加载机制的线程安全性:外部类加载时,静态内部类不会被加载;只有首次调用 getInstance() 时,JVM 才触发内部类加载和静态字段初始化,这个过程由 JVM 保证线程安全,且无须显式同步。
写法:
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}}
注意:这种方式依赖 JVM 对类初始化的规范实现,所有主流 JDK(7+)均可靠;但它比 DCL 多一次类加载开销,不过实际影响极小。
最容易被忽略的是:无论哪种方式,私有构造器必须存在且不可绕过——若忘了加 private,或没防御反射调用(比如在构造器里加 if (instance != null) throw new RuntimeException()),单例就可能被破坏。枚举是唯一不用额外防御的例外。










