Объяснение механизма взаимоблокировки инициализации класса - PullRequest
0 голосов
/ 08 декабря 2018

Я нашел статью (https://habr.com/company/odnoklassniki/blog/255067/) из @apangin (https://stackoverflow.com/users/3448419/apangin)) на моем родном языке, но я не могу ее понять. Объяснение очень краткое

Давайте рассмотрим, чтоcode:

static class A {
    static final B b = new B();
}

static class B {
    static final A a = new A();
}

public static void main(String[] args) {
    new Thread(A::new).start();
    new B();
}

Вы можете попытаться запустить этот код. На моем компьютере это приводит к тупику с вероятностью 75% /

Таким образом, у нас есть 2 потока:

Thread_1 isсоздание экземпляра A

Thread_2 (основной поток) создает экземпляр B

Это первый доступ к классу, поэтому он ведет (может привести) к одновременной инициализации классов A и B.

Следующий шаг для меня неясен. Не могли бы вы объяснить это?

Ответы [ 2 ]

0 голосов
/ 08 декабря 2018

Thread_1 создает экземпляр A

Да.И ключевое слово new требует инициализации класса A.

Thread_2 (основной поток) создает экземпляр B

Да.И ключевое слово new также требует инициализации класса B.

Но существует зависимость между этими классами в clinit (static блоки и static поля инициализации).


С JLS.12.4.2.Подробная процедура инициализации :

Для каждого класса или интерфейса C имеется уникальная блокировка инициализации LC.Отображение из C в LC оставлено на усмотрение реализации виртуальной машины Java.Тогда процедура инициализации C выглядит следующим образом:

  1. Синхронизация при блокировке инициализации , LC, для C. Это включает ожидание, пока текущий поток может получить LC .

(другие шаги опущены, но первый - самый важный)

Итак, вот что может произойти:

Thread_1:

  1. Видит, что он должен создать экземпляр класса A (new)
  2. Начинает инициализацию класса A (первое использование A класса)
  3. Получает блокировку инициализации класса A.

(Здесь планировщик потока ОС решает, что пришло время приостановить Thread_1 и дать некоторое время потоку main)

И поток main:

  1. Видит, что он должен создать экземпляр класса B (new)
  2. Начинает инициализацию класса B(первое использование класса B)
  3. Получает блокировку инициализации класса B.

Внешний видПланировщик объявлений теперь приостанавливает поток main и дает время Thread_1.

Thread_1 продолжается:

Он видит static final B b = new B(); Не разрешено создавать новый экземпляр B, поскольку класс B еще не инициализирован. Он пытается получить блокировку инициализации класса B, но блокировка удерживается потоком main.

Планировщик потока снова приостанавливает Thread_1 и дает некоторыевремя до main.И это продолжается:

Он видит static final A a = new A(); Не разрешено создавать новый экземпляр A, потому что класс B еще не инициализирован. Он пытается получить блокировку инициализации класса A, но блокировка удерживается потоком Thread_1.

Итак, вот тупик:

  1. Thread_1 содержит блокировку инициализации A и хочет получить блокировку инициализации класса B
  2. Поток main содержит блокировку инициализации B и хочет получить Aблокировка инициализации класса
0 голосов
/ 08 декабря 2018

Спецификация JVM §5.5 подробно описывает процедуру инициализации класса.Он состоит из 12 шагов.

На шаге 6 класс помечается как «в процессе инициализации текущим потоком».

В противном случае запишите тот факт, что текущий поток выполняет инициализацию объекта Class для C, и отпустите LC.

Итак, Thread_1 начинает инициализацию класса Aи помечает его как инициализированный Thread_1.Точно так же класс B помечается как инициализированный Thread_2.

На шаге 9 вызывается статический инициализатор.

Затем выполните метод инициализации класса или интерфейса для C.

Статический инициализатор A создает экземпляр B, который еще не полностью инициализирован, поэтомуThread_1 (в процессе инициализации A) рекурсивно запускает процедуру инициализации B.

. На шаге 2 процедуры обнаруживается, что класс B находится в процессе инициализации другим потоком.и блоки.

Если объект Class для C указывает, что инициализация для C выполняется каким-либо другим потоком, затем отпустите LC и заблокируйте текущий поток, пока не будет сообщено, что текущая инициализация завершена, и в этот момент повторите эту процедуру.

Симметрично Thread_2 запускает инициализацию A, обнаруживает, что она уже инициализируется другим потоком, а также блокирует на шаге 2. Оба потока блокируются в ожидании друг друга.

Примечание: класс помечается как полностью инициализированный и уведомляет другие потоки после успешного вызова статического инициализатора, что никогда не происходит в этом случае.

Если выполнение метода инициализации класса или интерфейса завершается нормально, затем получить LC, пометить объект Class для C как полностью инициализированный, уведомить все ожидающие потоки, освободить LC и завершить эту процедуру в обычном режиме.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...