Программа зависает, если поток создан в статическом блоке инициализатора - PullRequest
20 голосов
/ 22 сентября 2011

Я столкнулся с ситуацией, когда моя программа зависает, выглядит как тупик. Но я попытался выяснить это с помощью jconsole и visualvm, но они не обнаружили тупиков. Пример кода:

public class StaticInitializer {

private static int state = 10;

static {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            state = 11;
            System.out.println("Exit Thread");
        }
    });

    t1.start();

    try {
        t1.join();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    System.out.println("exiting static block");
}

public static void main(String...strings) {
    System.out.println(state);
}
}

Когда я выполняю это в режиме отладки, я вижу, что управление достигает @Override public void run () { состояние = 11;

но как только состояние = 11 выполняется, оно просто зависает / блокируется. Я просматривал различные сообщения в stackoverflow и думал, что статические инициализаторы поточно-ориентированы, но в этом случае jconsole должна сообщить об этом. Что касается основного потока, jconsole говорит, что он находится в состоянии ожидания, и это нормально. Но для потока, созданного в статическом блоке инициализатора, jconsole говорит, что он находится в состоянии RUNNABLE и не заблокирован. Я в замешательстве и здесь не хватает какой-то концепции. Пожалуйста, помогите мне.

Ответы [ 4 ]

33 голосов
/ 22 сентября 2011

Вы не просто начинаете другой поток - вы присоединяетесь к . Этот новый поток должен дождаться полной инициализации StaticInitializer, прежде чем он сможет продолжить, потому что он пытается установить поле state ... и инициализация уже выполняется, поэтому он ждет. Тем не менее, он будет ждать вечно, потому что эта инициализация ожидает завершения нового потока. Классический тупик.

См. Раздел Спецификация языка Java 12.4.2 для получения подробной информации о том, что входит в инициализацию класса. Важно отметить, что инициализирующий поток будет «владеть» монитором для StaticInitializer.class, но новый поток будет ожидать получения этого монитора.

Другими словами, ваш код немного похож на этот код не инициализатора (исключена обработка исключений).

final Object foo = new Object();
synchronized (foo)
{
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (foo) {
                System.out.println("In the new thread!");
            }
        });
    t1.start();
    t1.join();
});

Если вы понимаете, почему этот код блокируется, то для вашего кода это в основном то же самое.

Мораль не в том, чтобы много работать в статических инициализаторах.

13 голосов
/ 22 сентября 2011

загрузка классов является довольно чувствительным временем в jvm.когда классы инициализируются, они содержат внутреннюю блокировку jvm, которая приостанавливает любой другой поток, пытающийся работать с тем же классом.Итак, ваш порожденный поток, скорее всего, ожидает полной инициализации класса StaticInitializer, прежде чем продолжить.однако ваш класс StaticInitializer ожидает завершения потока, прежде чем будет полностью инициализирован.таким образом, тупик.

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

ОБНОВЛЕНИЕ:

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

5 голосов
/ 22 сентября 2011

Я смог запустить вашу программу, закомментировав строку state = 11;

Вы не можете установить состояние = 11, пока не завершите инициализацию. Вы не можете завершить инициализацию, пока t1 не закончит работу. T1 не может закончить работу, пока вы не установите состояние = 11. Тупик.

4 голосов
/ 22 сентября 2011

Вот что, я думаю, происходит:

  1. Основной поток пытается инициализировать StaticInitializer. Это включает блокировку соответствующего Class объекта.
  2. Удерживая блокировку, основной поток порождает другой поток и ожидает его завершения .
  3. Метод run() другого потока пытается получить доступ к state, что требует полной инициализации StaticInitializer; это включает ожидание на той же блокировке, что и на шаге 1.

Конечный результат: тупик.

См. JLS для подробного описания процедуры инициализации .

Если переместить t1.join() в main(), все работает.

...