静态内部类单例通过JVM类加载机制实现线程安全与延迟加载:Holder类仅在首次访问INSTANCE时初始化,启动时不创建对象;JVM保证clinit执行一次且安全发布实例;需私有构造、私有静态Holder类、极简getInstance方法。

Java 中用静态内部类实现单例,能同时做到线程安全和延迟加载,靠的不是代码加锁,而是 JVM 类加载机制本身的保障。
延迟加载是怎么发生的
Java 规范规定:类只有在首次“主动使用”时才会触发初始化。外部类(比如 Singleton)即使被加载进内存,它的静态内部类(比如 Holder)也不会初始化——直到代码中第一次读取 Holder.INSTANCE。
- 这个“第一次读取”就发生在 getInstance() 方法里
- 所以实例创建被完全推迟到真正调用 getInstance() 的那一刻
- 应用启动时不创建对象,不占内存,避免饿汉式浪费
- 编译后 Holder 是独立 class 文件(如 Singleton$Holder.class),没有对外部类的引用,利于 GC 回收
线程安全由 JVM 自动保证
当多个线程几乎同时调用 getInstance(),它们都会尝试访问 Holder.INSTANCE,从而触发 Holder 类的初始化。这时 JVM 会确保:
- 同一类加载器下,Holder 的初始化只执行一次
- JVM 自动对类初始化过程加锁,只有一个线程进入 <clinit> 方法,其余线程阻塞等待
- <clinit> 中的字段赋值与对象构造不可重排序,不会出现“对象逸出”
- public static final 字段在初始化完成时即安全发布,所有线程都能看到完整构造好的实例
标准写法不能少这三处
以下结构是生产环境可用的最小可靠形式,缺一不可:
立即学习“Java免费学习笔记(深入)”;
- 外部类构造方法必须私有:private Singleton() {},防止 new 调用,也为后续防反射留余地
- 静态内部类必须声明为 private static class Holder,且内部只含一个 public static final 实例字段
- getInstance() 方法体必须极简:return Holder.INSTANCE;,不判空、不加锁、不 new
要注意的限制和增强点
这个方案虽简洁高效,但也有边界条件需要留意:
- 不支持带参构造——因为 Holder 中只能调用无参构造
- 若构造方法抛异常,类加载失败,后续所有 getInstance() 都会抛 NoClassDefFoundError;建议在构造内做好容错处理
- 默认不防反射和反序列化破坏;如需强单例语义,可补充:在私有构造中检查已有实例、添加 readResolve() 方法


















