为何这两种单例实现与Initialization on Demand Holder(IODH)惯用法不同?
为什么直接静态初始化/静态块的单例与IODH惯用法不等价?
我正在学习Java内存模型(JMM),想理解为何以下两种使用静态变量和静态初始化器的单例实现方式,与Initialization on Demand Holder(IODH)惯用法并不等价。
选项1:直接初始化静态变量
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
选项2:静态初始化器
public class Singleton { private static Singleton instance = null; static { instance = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return instance; } }
我的认知依据:
- 《Java语言规范》(JLS)规定,静态初始化器仅在有人访问类时才会执行,静态变量的显式初始化也属于初始化的一部分,这同样适用于静态变量吗?
- 所有类均由类加载器按需加载。
请问我的认知误区在哪里?
解答
首先明确IODH惯用法的实现,方便对比:
public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }
你的认知误区集中在类初始化的触发时机以及静态内部类的独立性上:
1. 类"被访问"的范围远不止调用getInstance()
JLS规定的类初始化触发场景(主动使用)包括:
- 创建类的实例
- 访问类的静态字段(编译期常量除外)
- 调用类的静态方法
- 通过反射访问类
- 初始化子类时,父类会被先初始化
- 执行main方法的类
对于选项1和选项2,只要Singleton类被上述任意一种场景触发初始化,instance就会立即被创建。比如如果给Singleton类新增一个静态方法:
public static void logSomeInfo() { System.out.println("静态方法调用"); }
当其他代码调用Singleton.logSomeInfo()时,虽然完全不需要单例实例,但选项1和2的instance已经被创建了。
而IODH惯用法中,单例实例是放在静态内部类Holder中的。外部类Singleton的初始化不会触发内部类Holder的初始化,只有当主动访问Holder.INSTANCE(也就是调用getInstance()时),才会触发Holder类的初始化,进而创建单例实例——这才是真正的按需初始化(懒加载):只有当真正需要获取实例时才创建。
2. "类按需加载"不代表实例按需创建
你提到的"类由类加载器按需加载"是对的,但选项1和2的实例是在类加载完成后的初始化阶段就创建了,而IODH的实例是在静态内部类被主动使用时才创建,二者的初始化时机完全不同。
简单来说:选项1和2是"类初始化即创建实例",IODH是"获取实例时才触发内部类初始化进而创建实例",这就是核心差异。
内容的提问来源于stack exchange,提问作者ng.newbie
相关产品推荐
相关产品推荐

