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

时间:2019-06-15 15:09:18

标签: java multithreading concurrency deadlock static-initialization

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

它也导致死锁

1 个答案:

答案 0 :(得分:8)

新线程正在尝试在run类内调用Lock。该方法本身尝试创建Lock的新实例。在完成Lock类的初始化之前,它不能执行 1 。 JVM知道另一个线程已经在初始化该类,因此它将阻塞直到初始化完成。

不幸的是,由于run的调用,初始化工作要等到t.join()完成之后才能完成。因此,这两个线程中的每个线程都需要对方取得进展才能执行任何操作-死锁。

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

即使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处于封锁状态。