为什么从静态初始化程序启动线程并等待其完成会导致死锁?

Why starting thread from static initializer and awaiting its finishing leads to deadlock?

我从那个答案中提取代码-

我创建当前主题的原因是我不明白为什么该代码会导致死锁:

public class Lock implements Runnable {

    static {
        System.out.println(Thread.currentThread().getId() + "# Getting ready to greet the world");
        try {
            System.out.println(Thread.currentThread().getId() + "# before lock creation");
            Lock target = new Lock();
            System.out.println(Thread.currentThread().getId() + "# after lock creation");
            Thread t = new Thread(target);
            t.start();
            System.out.println(Thread.currentThread().getId() + "# awaiting thread finish");
            t.join();
            System.out.println(Thread.currentThread().getId() + "# static block finished");
        } catch (InterruptedException ex) {
            System.out.println("won't see me");
        }
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread() + "Hello World! ");
    }

    public void run() {
        System.out.println(Thread.currentThread().getId() + "# Started thread");
        Thread t = new Thread(new Lock());
        t.start();
    }
}

我尝试启动它很多次,但它总是导致死锁。

输出始终相同:

1# Getting ready to greet the world
1# before lock creation
1# after lock creation
1# awaiting thread finish
13# Started thread

我尝试使初始化程序成为非静态的,之后代码变得不会导致死锁。所以我相信它在某种程度上与静态 class 初始化有关。
你能解释一下吗?

回答

感谢 John Skeet 的回答,但为了简化事情,我删除了妨碍我理解该示例的代码行:

public class Lock implements Runnable {
    static {
        try {
            Thread thread = new Thread(new Lock());
            thread.start();
            thread.join();
        } catch (InterruptedException ex) {
            System.out.println("won't see me");
        }
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread() + "Hello World! ");
    }

    public void run() {
        new Lock();
    }
}

也导致死锁

新线程正在尝试在 Lock class 中调用 run。该方法本身尝试创建 Lock 的新实例。在 Lock class 完成初始化之前,它无法做到 1。 JVM 知道另一个线程已经在初始化 class,所以它会阻塞直到初始化完成。

不幸的是,由于 t.join() 调用,初始化在 run 完成之前无法完成。所以这两个线程中的每一个都需要对方取得进展才能做任何事情——死锁。

正是出于这个原因,避免在 class 初始化程序中做大量工作绝对值得。

即使 run() 方法本身为空,这一切都会发生。但它比那更糟糕 - 因为 run() 方法在创建另一个线程并等待 that 线程完成调用 run() 等之前不会完成。所以这是失败的另一个原因——它基本上会产生线程,直到 JVM 运行 资源不足。因此,即使删除类型初始值设定项也不会让您获得工作代码。

关于为什么需要类型初始化,参见section 12.4.1 of the JLS:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • A static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

表达式 new Lock() 将始终初始化 Lock,或等待初始化完成(当然,这可能已经发生)。


1如果您拆分 run 中的代码以便创建 Lock 的实例然后记录,然后启动线程,您你会看到它是 Lock 的创建阻塞。