Программа зависает, если поток создается в статическом блоке инициализатора

Я столкнулся с ситуацией, когда моя программа зависает, выглядит как тупик. Но я попытался понять это с помощью 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() {               state = 11;

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

Ответ 1

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

Подробнее о том, что включено в инициализацию класса, см. в разделе Language Language Specification section 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();
});

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

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

Ответ 2

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

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

UPDATE:

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

Ответ 3

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

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

Ответ 4

Вот что я думаю:

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

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

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

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