类的循环初始化是否会引起死锁?

2016/10/8   阅读 (O_O)?  

场景设计和推测

  • 情况:
    1. 在类A中的初始化中实例化B
    2. 在类B的初始化中实例化A
  • 类设计
    • A类:
      • 静态变量a=new B();静态变量a1=1(之后在静态初始化块里赋值为2);
      • 实例变量a2=11(之后再初始化块中赋值为12);
      • 构造函数;
    • B类:
      • 静态变量b=new A();静态变量b1=3(之后在静态初始化块里赋值为4);
      • 实例变量b2=21(之后再初始化块中赋值为22);
      • 构造函数;
  • 猜想执行结果: 由于类初始化之后类实例化,所以A类初始化需要B实例化,B实例化又需要A初始化,造成循环依赖,最终结果为死锁
  • 打点位置:
    1. 类加载结束点(text: Loaded Main2 from file)
    2. 类初始化开始点/结束点(text: Class A2 init)
    3. 实例初始化开始点/结束点(text: Instance A2 init)
    4. 构造函数结束点(text: Instance A2 new)

场景代码

class A2 {
    static {
        System.out.println("Class A2 init start");
    }

    static B2 a = new B2();
    static int a1 = 1;

    {
        System.out.println("Instance A2 init start. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2));
    }

    public int a2 = 11;

    static {
        a1 = 2;
        System.out.println("Class A2 init end. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2));
    }

    {
        a2 = 12;
        System.out.println("Instance A2 init end. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2));
    }

    public A2() {
        System.out.println("Instance A2 new. \ta=" + a + " \ta1=" + a1 + " \ta.b2=" + (a == null ? "NPE" : a.b2) + " \tb=" + B2.b + " \tb1=" + B2.b1 + " \tb.a2=" + (B2.b == null ? "NPE" : B2.b.a2));
    }
}

class B2 {
    static {
        System.out.println("Class B2 init start");
    }

    static A2 b = new A2();
    static int b1 = 3;

    {
        System.out.println("Instance B2 init start. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2));
    }

    public int b2 = 21;

    static {
        b1 = 4;
        System.out.println("Class B2 init end. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2));
    }

    {
        b2 = 22;
        System.out.println("Instance B2 init end. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2));
    }

    public B2() {
        System.out.println("Instance B2 new. \tb=" + b + " \tb1=" + b1 + " \tb.a2=" + (b == null ? "NPE" : b.a2) + " \ta=" + A2.a + " \ta1=" + A2.a1 + " \ta.b2=" + (A2.a == null ? "NPE" : A2.a.b2));
    }
}

class Main2 {
    static public void main(String... args) {
        System.out.println("A2 a=" + A2.a);
        System.out.println("A2 a1=" + A2.a1);
        System.out.println("A2 a2=" + B2.b.a2);
        System.out.println("B2 b=" + B2.b);
        System.out.println("B2 b1=" + B2.b1);
        System.out.println("B2 b2=" + A2.a.b2);
    }
}

执行结果分析

程序输出结果:

1. [Loaded Main2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]
2. [Loaded A2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]
3. Class A2 init start
4. [Loaded B2 from file:/Users/jiadongy/JVM_Learning_Sample/out/production/JVM_Learning_Sample/]
5. Class B2 init start
6. Instance A2 init start. a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE
7. Instance A2 init end.     a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE
8. Instance A2 new.         a=null a1=0 a.b2=NPE b=null b1=0 b.a2=NPE
9. Class B2 init end.            b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
10. Instance B2 init start.       b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
11. Instance B2 init end.        b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
12. Instance B2 new.            b=A2@61bbe9ba b1=4 b.a2=12 a=null a1=0 a.b2=NPE
13. Class A2 init end.   a=B2@610455d6 a1=2 a.b2=22 b=A2@61bbe9ba b1=4 b.a2=12
14. A2 a=B2@610455d6
15. A2 a1=2
16. A2 a2=12
17. B2 b=A2@61bbe9ba
18. B2 b1=4
19. B2 b2=22

把它转化为下面的表格,更加清晰地描述A/B各个阶段执行的过程:

A B
A类加载完成
A类初始化 - 开始
B类加载完成
B类初始化 - 开始
A类实例初始化 - 开始
A类实例初始化 - 结束
A类实例构造函数执行完成
B类初始化 - 结束
B类实例初始化 - 开始
B类实例初始化 - 结束
B类实例构造函数执行完成
A类初始化 - 结束

可以看到在A类初始化的过程中,A类被实例化了(并且该阶段正常结束了),也就是说类的初始化阶段并不是原子的/排他的.
如在本例中,A类实例化阶段的结束早于其类初始化阶段,A类实例化完成时,A类的静态变量还未被初始化.

Reference.1中已经描述了这种情况:

加载/验证/准备/初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始...注意,这里笔者写的是按部就班的"开始",而不是按部就班的"进行"或"完成",强调这点是因为这些阶段通都是相互交叉混合式进行的,通常会在一个阶段执行的过程中调用/激活另外一个阶段
<<深入理解Java虚拟机>>P210

总结

  1. 类的循环初始化不会引起死锁
  2. 5个阶段的开始是有顺序的,结束则不一定
  3. 阶段不是排他的/临界的
  4. 循环初始化可能引起意料之外的情况,尽量避免
    • eg.类在初始化过程中修改另一个类的变量,导致另一个类得到了意料之外的初始值

Reference

  1. 深入理解Java虚拟机