Почему запуск потока из статического инициализатора и ожидание его завершения приводит к тупику? - PullRequest
2 голосов
/ 15 июня 2019

Я беру код из этого ответа - 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

Я попытался сделать инициализатор нестатическим, и после этого код стал не приводить к тупиковой ситуации.Поэтому я считаю, что это как-то связано со статической инициализацией класса.
Не могли бы вы объяснить это?

Ответ

Спасибо Джону Скиту за ответ, но для упрощения я удалил строки кода, которые не позволили мнепонять этот пример:

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 Ответ

8 голосов
/ 15 июня 2019

Новый поток пытается вызвать run в классе Lock. Этот метод сам пытается создать новый экземпляр Lock. Он не может сделать это 1 , пока класс Lock не завершит инициализацию. JVM знает, что другой поток уже инициализирует класс, поэтому он блокируется, пока эта инициализация не будет завершена.

К сожалению, эта инициализация не может быть завершена до завершения run из-за вызова t.join(). Таким образом, каждый из двух потоков нуждается в другом, чтобы добиться прогресса, прежде чем он сможет что-либо сделать - тупик.

Определенно стоит попытаться избежать большой работы в инициализаторах классов, именно по этой причине.

Это могло бы произойти, даже если сам метод run() был бы пустым. Но это еще хуже - потому что метод run() не завершится, пока не создаст другой поток и не дождется, пока этот поток завершит вызов run() и т. Д. Так что это еще одна причина сбоя - он будет в основном порождают потоки, пока у JVM не закончились ресурсы. Таким образом, даже удаление инициализатора типа не приведет вас к рабочему коду.

Что касается инициализации типа, см. раздел 12.4.1 JLS :

Класс T или интерфейс типа T будут инициализированы непосредственно перед первым возникновением любого из следующих действий:

  • T является классом, и создается экземпляр T.
  • Вызывается статический метод, объявленный T.
  • Назначено статическое поле, объявленное T.
  • Используется статическое поле, объявленное T, и поле не является константной переменной (§4.12.4).

Выражение new Lock() всегда будет либо инициализировать Lock, либо ждать завершения инициализации (что, конечно, уже могло произойти).


1 Если вы разделите код в run, чтобы создать экземпляр Lock, а затем войти в систему, а затем запустить поток, вы увидите, что это создание Lock, это блокировки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...