Вчера я заметил что-то очень странное. Кажется, что два потока вводят два синхронизируемых блока, соединяющие тот же объект одновременно.
Класс (MyClass
) содержание соответствующих норм выглядит подобным этому:
private static int[] myLock = new int[0];
protected static int methodA(final long handle, final byte[] sort) {
synchronized (myLock) {
return xsMethodA(handle, sort);
}
}
protected static int methodB(final long handle) {
synchronized (myLock) {
return xsMethodB(handle);
}
}
Я создал дамп потока своего приложения, выполняющего вышеупомянутый класс, и был очень удивлен, поскольку я видел это:
"http-8080-136" daemon prio=10 tid=0x00000000447df000 nid=0x70ed waiting for monitor entry [0x00007fd862aea000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.MyClass.methodA(MyClass.java:750)
- locked <0x00007fd8a6b8c790> (a [I)
at com.SomeOtherClass.otherMethod(SomeOtherClass.java:226)
...
"http-8080-111" daemon prio=10 tid=0x00007fd87d1a0000 nid=0x70c8 waiting for monitor entry [0x00007fd86e15f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.MyClass.methodB(MyClass.java:991)
- locked <0x00007fd8a6b8c790> (a [I)
at com.SomeOtherClass.yetAnotherMethod(SomeOtherClass.java:3231)
...
(Я изменил имена классов и имена методов для случая простоты, не запутывайтесь глупыми именами.)
Кажется, что поток http-8080-136 и http-8080-111 оба получил блокировку на myLock
. Это - тот же объект, как объектный адрес является тем же: 0x00007fd8a6b8c790
. Спецификация Среды выполнения Java говорит это о synchronized
ключевое слово:
Синхронизированный оператор получает блокировку взаимного исключения (§17.1) от имени выполняющегося потока, выполняет блок, затем выпускает блокировку. В то время как выполняющийся поток владеет блокировкой, никакой другой поток не может получить блокировку. [Спецификация языка Java, 14.19]
Таким образом, как это даже возможно?
Существуют еще 44 потока в дампе потока, "ожидающем" блокировки. Это - то, как похоже, ожидает ли поток:
"http-8080-146" daemon prio=10 tid=0x00007fd786dab000 nid=0x184b waiting for monitor entry [0x00007fd8393b6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.MyClass.methodC(MyClass.java:750)
- waiting to lock <0x00007fd8a6b8c790> (a [I)
at com.SomeOtherClass.yetAnoterMethod2(SomeOtherClass.java:226)
Я задал тот же вопрос в списке рассылки hotspot-dev и получил очень интересный ответ от Кристофера Филлипса:
Привет Эдуард
Я думаю, что дамп потока вводит в заблуждение.
Если вы действительно думаете, что эти 2 потока находятся в блокировке одновременно, вам, вероятно, следует получить gcore (который является внешне последовательным).
Состояние, которое вы видите "ожидание входа в монитор", на самом деле MONITOR_WAIT, которое может представлять следующий код перед фактическим получением горячей блокировки: (также см. OSThreadContendState в osThread.hpp), вызываемый из: src/share/vm/runtime/synchronizer.cpp
3413 OSThreadContendState osts(Self->osthread());
3414 ThreadBlockInVM tbivm(jt);
3415
3416 Self->set_current_pending_monitor(this);
3417
3418 // TODO-FIXME: change the following for(;;) loop to straight-line code.
3419 for (;;) {
3420 jt->set_suspend_equivalent();
3421 // cleared by handle_special_suspend_equivalent_condition()
3422 // or java_suspend_self()
3423
3424 EnterI (THREAD) ;
3425
3426 if (!ExitSuspendEquivalent(jt)) break ;
3427
3428 //
3429 // We have acquired the contended monitor, but while we were
3430 // waiting another thread suspended us. We don't want to enter
3431 // the monitor while suspended because that would surprise the
3432 // thread that suspended us.
Chris
Как был сделан дамп потока? Если бы потоки не были приостановлены, владение блокировкой могло измениться между сбросом одного потока на другой.
Я думаю, что важная информация такова: «ожидание записи монитора», что одинаково для обоих потоков. Поскольку оба потока (в дампе потока) помечены как потоки deamon, я предполагаю, что одновременно должен быть запущен основной поток. Возможно ли, что основной поток является текущим владельцем монитора, который блокирует два других потока?
Они не получили блокировку, иначе вы увидите xsMethodA или xsMethodB в трассировке стека.