静态代码块在类加载时立即执行,属饿汉式;延迟加载通过静态内部类Holder实现,仅首次调用getInstance()时初始化,属懒汉式,二者执行时机根本对立。

静态代码块在类加载时立即执行,而延迟加载要等到首次调用 getInstance() 才创建实例——这两者本质对立。理解它们的执行时机差异,是掌握单例模式设计取舍的关键。
静态代码块:类加载即触发,不延迟
静态代码块属于“饿汉式”的底层支撑机制。JVM 在首次主动使用该类(如调用静态方法、访问静态字段)前,必须完成类的初始化,其中就包括执行所有静态代码块。
- 哪怕你从没调用过 getInstance(),只要 Class 被加载并初始化,静态块里的 new Singleton() 就已执行
- 它无法感知业务是否“真正需要”这个实例,资源占用是刚性的
- 适合配置预加载、工具类初始化等确定性场景,比如读取配置文件后构建单例
延迟加载:靠 Holder 类隔离,绕过类初始化时机
静态内部类方案之所以能延迟,核心在于 JVM 规范对“类初始化”的定义:外部类 Singleton 的加载 ≠ 内部类 Holder 的初始化。Holder 只有在 getInstance() 中首次引用 Holder.INSTANCE 时,才被触发初始化。
- 外部类可被多次加载、反射获取,但 Holder 不被访问就不会初始化
- INSTANCE 的创建被“藏”在 Holder 的静态字段初始化中,而该过程由 JVM 保证线程安全且仅执行一次
- 没有 synchronized、volatile 或双重检查,性能接近直接读取静态字段
实战对比:看字节码和运行时行为
写两个版本并观察输出顺序:
// 版本 A:含静态块的饿汉式class SingletonEager {
static { System.out.println("【静态块执行】"); }
private static final SingletonEager instance = new SingletonEager();
private SingletonEager() { System.out.println("【构造函数执行】"); }
public static SingletonEager getInstance() { return instance; }
}
class SingletonLazy {
private SingletonLazy() { System.out.println("【构造函数执行】"); }
private static class Holder {
static final SingletonLazy instance = new SingletonLazy();
}
public static SingletonLazy getInstance() { return Holder.instance; }
}
执行 System.out.println(SingletonEager.class); —— 会立刻打印“静态块执行”和“构造函数执行”;
执行 System.out.println(SingletonLazy.class); —— 什么都不会输出;
只有执行 SingletonLazy.getInstance() 后,“构造函数执行”才出现。
不是非此即彼,而是按需组合
静态块和延迟加载并非互斥工具,而是不同粒度的控制手段:
- 若需在单例创建前加载配置、校验环境,可在 Holder 内部类里加静态块(不影响外层类)
- 若整个单例依赖某外部服务启动,可用延迟加载 + 初始化检查(如 getInstance 中判断状态再 throw)
- 静态块适合“无条件前置准备”,延迟加载适合“按需兑现承诺”











