首页 > 解决方案 > 为什么从静态初始化程序启动线程并等待其完成会导致死锁?

问题描述

我从那个答案中获取代码 - https://stackoverflow.com/a/9286697/2674303

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

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

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

回答

感谢 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();
    }
}

也会导致僵局

标签: javamultithreadingconcurrencydeadlockstatic-initialization

解决方案


新线程试图runLock类内调用。该方法本身尝试创建Lock. 在类完成初始化之前,它不能这样做1 。LockJVM 知道另一个线程已经在初始化这个类,所以它会阻塞直到初始化完成。

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

正是出于这个原因,绝对值得尝试避免在类初始化器中做大量工作。

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

关于为什么需要类型初始化,请参阅JLS 的第 12.4.1 节

类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:

  • T 是一个类,并创建了一个 T 的实例。
  • 调用由 T 声明的静态方法。
  • 分配了一个由 T 声明的静态字段。
  • 使用了由 T 声明的静态字段,并且该字段不是常量变量(第 4.12.4 节)。

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


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


推荐阅读