Как диагностировать или обнаруживать взаимоблокировки в статических инициализаторах Java
(Является ли использование статических инициализаторов в Java хорошей идеей, выходит за рамки этого вопроса.)
Я встречаюсь с тупиками в моем приложении Scala, которое, как мне кажется, вызвано блокировкой статических инициализаторов в скомпилированных классах.
Мой вопрос заключается в том, как обнаружить и диагностировать эти взаимоблокировки. Я обнаружил, что обычные инструменты JVM для взаимоблокировок не работают, когда задействуются блоки статического инициализатора.
Вот простой пример приложения Java, который блокирует статический инициализатор:
public class StaticDeadlockExample implements Runnable
{
static
{
Thread thread = new Thread(
new StaticDeadlockExample(),
"StaticDeadlockExample child thread");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args)
{
System.out.println("in main");
}
public static void sayHello()
{
System.out.println("hello from thread " + Thread.currentThread().getName());
}
@Override
public void run() {
StaticDeadlockExample.sayHello();
}
}
Если вы запустите это приложение, оно будет заблокировано. Трассировка стека во время тупиковой ситуации (от jstack
) содержит следующие два тупиковых потока:
"StaticDeadlockExample child thread" prio=6 tid=0x000000006c86a000 nid=0x4f54 in Object.wait() [0x000000006d38f000]
java.lang.Thread.State: RUNNABLE
at StaticDeadlockExample.run(StaticDeadlockExample.java:37)
at java.lang.Thread.run(Thread.java:619)
Locked ownable synchronizers:
- None
"main" prio=6 tid=0x00000000005db000 nid=0x2fbc in Object.wait() [0x000000000254e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000004a6a7870> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1143)
- locked <0x000000004a6a7870> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1196)
at StaticDeadlockExample.<clinit>(StaticDeadlockExample.java:17)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:116)
Locked ownable synchronizers:
- None
Мои вопросы следующие
- Почему первый поток отмечен как RUNNABLE, когда он действительно ждет блокировки? Могу ли я каким-то образом определить "реальное" состояние этого потока?
- Почему ни один поток не отмечен как владелец каких-либо (релевантных) блокировок, когда на самом деле один хранит статический блокиратор intializer, а другой ожидает его? Могу ли я каким-то образом обнаружить блокировку блокировки инициализатора?
Ответы
Ответ 1
Scala позволяет легко попасть в ловушку.
Легкое обходное решение или диагностика (если вы видите клинику в трассировке стека) - это позволить вашему объекту расширить приложение, чтобы DelayedInit удалил ваш код из статического инициализатора.
Некоторые поясняющие ссылки:
https://issues.scala-lang.org/browse/SI-7646
Scala: Параллельная коллекция в инициализаторе объектов заставляет программу зависать
http://permalink.gmane.org/gmane.comp.lang.scala.user/72499
Ответ 2
Я пробовал этот пример с мой инструмент, и он также не смог обнаружить это как тупик. После нескольких попыток с отладчиком jconsole и повторного запуска примера пару раз я заметил, что начальный поток отмечен как RUNNABLE, потому что он запущен, проблема в том, что с момента запуска потока доступа к статическому члену эта операция помещается в очередь после завершения статического блока инициализатора (эта семантика не ясна в спецификации JVM, однако, похоже, это так).
Статический инициализатор не заканчивается, потому что в этом причудливом примере операция соединения заставляет его ждать завершения потока, однако я замечаю, что эта операция "в очереди" не захватывает блокировку явно или неявно в соответствии со спецификацией JVM. Возможно, это не следует рассматривать как тупик как таковой, поскольку это будет тот же самый случай, если тело метода run содержит бесконечный цикл.