为何单例实例持有者的循环引用不会抛出StackOverflowError?
为什么互相依赖的Holder单例会出现null引用而非栈溢出?
先看这段代码:
static class A { private static class InstanceHolder { private static final A a = new A(); } private final B b; private A() { b = B.getInstance(); } static A getInstance() { return InstanceHolder.a; } } static class B { private static class InstanceHolder { private static final B b = new B(); } private final A a; private B() { a = A.getInstance(); } static B getInstance() { return InstanceHolder.b; } } public static void main(String[] args) { A a = A.getInstance(); B b = B.getInstance(); System.out.println(a.b); System.out.println(b.a); }
你原本以为A→B→A的递归构造会触发StackOverflowError,但实际结果是a.b能拿到B的实例,b.a却是null,核心原因在Java类的静态初始化规则:
具体执行流程
- main方法调用
A.getInstance(),触发A$InstanceHolder静态内部类的初始化——Holder模式就是靠静态内部类的懒加载实现单例。 A$InstanceHolder的静态代码执行new A(),开始构造A实例,进入A的构造方法。- A的构造里调用
B.getInstance(),触发B$InstanceHolder的初始化,执行new B()构造B实例。 - B的构造里调用
A.getInstance(),这时候关键来了:A$InstanceHolder已经处于正在初始化的状态——它的静态变量a还在执行new A()的赋值过程,还没把构造好的A实例赋值给a,此时a的默认值是null。 - 根据JVM规则,当一个类正在初始化时,递归访问它的静态成员不会重新触发初始化,而是直接返回该成员的默认初始值(这里就是null)。所以B的构造里
a被赋值为null,B实例构造完成后返回给A的构造方法,A的b拿到这个B实例,A实例构造完成后赋值给A$InstanceHolder.a。 - 回到main方法调用
B.getInstance(),直接返回已经初始化好的B实例,最终输出自然是a.b有效,b.a为null。
为什么没出现栈溢出?
因为在B的构造调用A.getInstance()时,并没有再次触发A实例的构造,只是返回了还没完成赋值的静态变量null,递归到这里就终止了,不会无限循环调用构造方法,也就不会出现StackOverflowError。
内容的提问来源于stack exchange,提问作者Leib Rozenblium
相关产品推荐
相关产品推荐

