
本文详解singleton单例模式中饿汉式(eager)与懒汉式(lazy)初始化的执行时机差异,通过类加载机制、静态块验证和实际运行日志,清晰揭示二者在实例创建时间点上的根本不同。
本文详解singleton单例模式中饿汉式(eager)与懒汉式(lazy)初始化的执行时机差异,通过类加载机制、静态块验证和实际运行日志,清晰揭示二者在实例创建时间点上的根本不同。
在Java中,Singleton模式的两种经典实现——饿汉式初始化(Eager Initialization) 和 懒汉式初始化(Lazy Initialization),其核心差异并非表面代码结构之别,而在于JVM类加载与初始化阶段的语义差异:前者在类首次被主动使用(如调用静态方法)前,即完成类加载、链接与静态字段初始化;后者则将实例创建推迟至第一次调用 getInstance() 时才发生。
关键在于理解Java类生命周期中的类初始化(Initialization)阶段:当一个类首次被“主动使用”(如调用其静态方法、访问其静态字段、反射获取Class对象等),JVM会确保该类已被加载、链接,并执行其静态初始化块(static block)和静态字段赋值语句。而饿汉式正是利用了这一机制:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton(); // ✅ 类初始化时立即执行
static {
System.out.println("EagerSingleton instantiated"); // 在instance创建后立即输出
}
private EagerSingleton() { /* 私有构造防止外部实例化 */ }
public static EagerSingleton getInstance() {
System.out.println("EagerSingleton returned instance");
return instance;
}
}此处 instance = new EagerSingleton() 是静态字段的显式初始化表达式,它与 static {} 块一同在类初始化阶段按文本顺序执行。因此,只要 EagerSingleton 类因任何原因被首次主动使用(例如调用 getInstance()),其 instance 必然已在该方法执行前完成创建。
反观懒汉式:
public class LazySingleton {
private static LazySingleton instance; // ❌ 仅声明,无初始化表达式
static {
System.out.println("LazySingleton not instantiated"); // 类初始化时执行,但instance仍为null
}
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
System.out.println("LazySingleton checking instance");
if (instance == null) {
System.out.println("LazySingleton instance assigned");
instance = new LazySingleton(); // ✅ 首次调用时才执行
}
System.out.println("LazySingleton returned instance");
return instance;
}
}instance 字段仅声明未初始化,其默认值 null 在类加载后即存在。static {} 块虽在类初始化时执行,但不触发实例创建。真正创建发生在 getInstance() 的 if 分支内——这是运行时逻辑判断,而非类加载机制的一部分。
通过以下测试可直观验证时序差异:
System.out.println("e1");
EagerSingleton e1 = EagerSingleton.getInstance(); // 输出:instantiated → returned instance
System.out.println("l1");
LazySingleton l1 = LazySingleton.getInstance(); // 输出:not instantiated → checking → assigned → returned
System.out.println("e2");
EagerSingleton e2 = EagerSingleton.getInstance(); // 输出:returned instance(instantiated 不再重复)
System.out.println("l2");
LazySingleton l2 = LazySingleton.getInstance(); // 输出:checking → returned(assigned 不再重复)输出结果明确显示:
-
EagerSingleton instantiated出现在e1调用getInstance()之前(即类初始化阶段); -
LazySingleton not instantiated出现在l1调用前,但instance assigned直到checking instance后才发生; - 后续调用均不再触发初始化或实例创建,印证单例特性。
⚠️ 注意事项:
- 饿汉式线程安全天然,但可能造成资源浪费(如单例依赖重型资源且实际永不使用);
- 懒汉式需
synchronized保证线程安全(或采用双重检查锁/DCL、静态内部类等优化),否则存在竞态风险; - 真正的“延迟”取决于类是否被提前主动引用(如其他类
import并静态访问其字段),而不仅限于getInstance()调用。
综上,二者差异本质是编译期绑定(饿汉)vs 运行时判定(懒汉)。理解JVM类初始化规范,是准确把握这一设计取舍的关键。

















