【问题标题】:Why starting thread from static initializer and awaiting its finishing leads to deadlock?为什么从静态初始化程序启动线程并等待其完成会导致死锁?
【发布时间】:2019-06-15 15:09:18
【问题描述】:

我从那个答案中获取代码-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();
    }
}

也会导致死锁

【问题讨论】:

  • @Turing85: 不,new Lock()run 方法和静态初始化器 - 不在构造函数中。那里没有递归。 (如果不是死锁,它会因为线程太多而耗尽内存,但它不会是堆栈溢出。)
  • @JonSkeet Oh there is a recursion (although it throws an OutOfMemoryException)。如果run() 被执行(如果线程被启动),那么一个新的Lock-object 被创建,它 - 再次 - 在一个单独的线程中启动。
  • 你不应该在静态初始化器中做这样的事情。它们本质上被特别锁定并在意外情况下运行。此外,它使测试、错误处理和调试变得非常困难。
  • @eckes 这是显而易见的事实
  • 并且运行一个单独的线程并等待其终止与直接运行它是等效的(对于大多数目的)new Lock().run();

标签: java multithreading concurrency deadlock static-initialization


【解决方案1】:

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

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

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

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

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

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

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

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


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

【讨论】:

  • 所以 thread#1 开始初始化类并创建第二个线程 (thread#13) 但 thread13 可以' t 出于某种原因创建另一个线程。为什么?
  • @gstackoverflow:正在编辑答案。
猜你喜欢
  • 2016-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-14
相关资源
最近更新 更多