JVM的BUG!?Java类静态初始化里启线程,居然导致main函数阻塞!!!

先来看这个代码是咋写的

public class Lazy {
    public static boolean init = false;

    static {
        Thread t = new Thread(() -> init = true);
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println(init);
    }
}

原问题是最终的输出结果,但实际上什么都不会输出。

我们先给自己创建的线程起个名字,叫"STATIC_JOIN",再通过IDEA看看线程的状态。

JVM的BUG!?Java类静态初始化里启线程,居然导致main函数阻塞!!!_第1张图片

JVM的BUG!?Java类静态初始化里启线程,居然导致main函数阻塞!!!_第2张图片

我们发现main线程的确阻塞了,然后"STATIC_JOIN"线程一直处在runnable状态,似乎一直在等待着什么。

我们一个个来思考,为什么main线程阻塞了?其实这个不难推测,通过控制台代码定位就能得知,是因为线程join方法的关系。

JVM的BUG!?Java类静态初始化里启线程,居然导致main函数阻塞!!!_第3张图片

join方法的作用,就是阻塞当前线程,让调用join方法的线程先执行完毕。既然main线程一直阻塞着,说明"STATIC_JOIN"线程一直没有执行完毕。那问题来了,"STATIC_JOIN"线程里的操作非常简单,为啥会一直无法结束呢?我们发现,线程里的操作,仅仅是对类静态变量赋值操作,难道是这里出了问题?当我去了里面的操作,留下一个空的Lambda表达式,执行发现依旧会阻塞。这时,我又猜测,是否是Lambda表达式的关系,又将代码改成了下面这样。

public class Lazy {
    public static boolean init = false;

    static {
//        Thread t = new Thread(() -> {});
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                // 注释了下面这行代码,则不会阻塞
                init = true;
            }
        });
        t.setName("STATIC_JOIN");

        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println(init);
    }
}

我发现,改成匿名内部类的形式,只要不操作静态变量,就不会有阻塞问题。此时,我们就可以得出一个猜想:

当要使用一个类的静态变量时,必须先初始化这个类;但初始化类时,必须执行完静态初始化代码块。第一个准备去执行静态初始化代码块的线程,当然是main线程,但很遗憾,它因为join方法阻塞住了。此时,当"STATIC_JOIN"线程调用静态变量时,发现该类没初始化好,就也会去尝试初始化。但发现已经有其它线程(即main线程)获取到了它的初始化锁,那么"STATIC_JOIN"线程就一直处于runnable状态,因而线程会无法执行完毕,这又反过来导致main函数阻塞。

JVM的BUG!?Java类静态初始化里启线程,居然导致main函数阻塞!!!_第4张图片

那Lambda表达式,又作何解释呢。没办法,只能看字节码了。

JVM的BUG!?Java类静态初始化里启线程,居然导致main函数阻塞!!!_第5张图片

编译完类后,通过javap命令查看类的成员属性,发现Lambda表达式子编译后,会生成一个静态方法,所以调用Lambda表达式,本质上就是调用静态方法。调用静态方法与使用静态变量一样,需要类初始化完毕,所以使用了Lambda表达式,就会导致阻塞。

Ps:javap -c -verbose Lazy.class,可以查看字节码详细内容;一个类中使用了匿名内部类,编译后,会多生成一份匿名内部类的class文件

你可能感兴趣的:(Java)