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

Я столкнулся с ситуацией, когда моя программа зависает, выглядит как тупик. Но я попытался понять это с помощью 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, и я думал, что статические инициализаторы являются streamобезопасными, но в этом случае jconsole должен сообщить об этом. В основном streamе, jconsole говорит, что он находится в состоянии ожидания, и все в порядке. Но для streamа, созданного в статическом блоке инициализатора, jconsole говорит, что он находится в состоянии RUNNABLE и не заблокирован. Я смущен и здесь не хватает понятия. Пожалуйста, помогите мне.

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

    Подробные сведения о том, что задействовано в инициализации classа, см. В разделе 12.4.2 Language Language Specification . Важно отметить, что инициализирующий stream будет «владеть» монитором для StaticInitializer.class , но новый stream будет ожидать получения этого монитора.

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

     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(); }); 

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

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

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

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

    ОБНОВИТЬ:

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

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

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

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

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

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

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

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

    Давайте будем гением компьютера.