IllegalMonitorStateException для функции awaitTermination
У меня проблема с использованием потоков в Java (у меня мало опыта работы с потоками в Java, но много в C++, поэтому я понимаю основную концепцию потоков). Я использовал пример кода для потоков в Java, и код следующий:
ExecutorService executor = Executors.newFixedThreadPool(machines.size());
for (Machine m : machines) {
Runnable worker = new restartMachine(m.dataformachine());
executor.execute(worker);
}
executor.shutdown();
try {
executor.awaitTermination(15, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
restartMachine()
перезапускает некоторые удаленные машины, и машины никак не связаны, данные, которые передаются в Runnable, являются IP-адресом для данной машины и командой, которая затем выполняется локально на этой машине.
Ошибка, которую я получаю при выполнении этого фрагмента кода, следующая:
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1471)
Исключение выдается при вызове функции awaitTermination() из кода выше. Как я понимаю, и из различных примеров, которые я видел, не должно быть никаких проблем с этим кодом.
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
Трассировка указывает на ошибку при вызове функции mainLock.unlock(); но, насколько я понимаю, только основной поток будет выполнять эту строку, поэтому я не знаю, почему я получаю IllegalMonitorStateException, и нет другого кода относительно потоков в программе (поэтому я в основном использую код только из библиотеки)
Я был бы признателен за любую помощь, я знаю, что уже есть много вопросов относительно этой проблемы (это исключение), но я не знаю, в чем здесь проблема.
2 ответа
Это очень своеобразно, и, вероятно, не ваша вина
Javadoc из ReentrantLock.unlock говорит:
выдает IllegalMonitorStateException, если текущий поток не удерживает эту блокировку
но реализация awaitTermination
вы опубликовали показывает, что поток успешно заблокировал тот же объект (через последнюю переменную mainLock
) ранее. Следовательно, была промежуточная разблокировка, или в реализации ReentrantLock есть ошибка (в ее Java-коде, или в нативном коде, или, возможно, даже в харде). Необходим дальнейший анализ, чтобы выяснить, в чем дело. Поскольку в настоящее время вы единственный, кто может воспроизвести проблему, вы единственный, кто может эффективно выполнить этот анализ.
Разумным первым шагом будет запуск приложения в режиме отладки и установка точки останова в AbstractOwnableSynchronizer.setExclusiveOwnerThread
проверить, была ли промежуточная разблокировка (и если да, то откуда). Если наличие точки останова приводит к исчезновению проблемы (поскольку она зависит от времени), вы можете использовать условную точку останова, которая никогда не останавливается, но чье состояние записывается в System.out для проверки.
Обновление Благодаря репортеру, предоставленному Андремонием в его ответе, я смог выполнить этот анализ самостоятельно. Я использовал следующее выражение в условной точке останова для получения трассировки стека всякий раз, когда блокируется или освобождается блокировка:
new RuntimeException(this + " is now owned by " + arg0).printStackTrace();
return false;
Вот соответствующая часть вывода журнала для его кода:
java.lang.RuntimeException: java.util.concurrent.locks.ReentrantLock$NonfairSync@a5e3519[State = 1, empty queue] is now owned by null
at java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread(AbstractOwnableSynchronizer.java:74)
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2069)
at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1465)
at stackru.Test$1.run(Test.java:24)
at java.lang.Thread.run(Thread.java:745)
...
java.util.concurrent.locks.ReentrantLock$NonfairSync@a5e3519[State = 0, empty queue] could not be released, as it is owned by null rather than Thread[Thread-0,5,main]
То есть, исполнитель выпустил, но не получил, mainLock
в awaitNanos
, который реализован следующим образом:
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
Как видно из отсутствия блока finally, метод не является безопасным для исключения, т.е. блокировка не восстанавливается при возникновении исключения (например, ThreadDeathException
вызванный Thread.stop()
).
Вы можете сообщить об этой ошибке в Oracle. Тем не менее, поскольку он проявляется только при использовании устаревшего API, и влияние довольно незначительное (создается неправильный тип исключения), они могут его не исправить.
Эта проблема может быть легко воспроизведена, если мы обернем ваш код в некоторые Thread
а затем вызвать его устаревший (только для демонстрации проблемы) метод stop
Например:
private void method() throws InterruptedException {
Runnable runnable = new Runnable() {
public void run() {
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executor.shutdown();
try {
executor.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(1000L);
thread.stop();
}
Запустив этот код, мы всегда получаем "желаемое" исключение:
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1471)
at q29431344.TestThreads$1.run(TestThreads.java:37)
at java.lang.Thread.run(Thread.java:724)
Что это значит?
Без просмотра полного кода проекта (разумеется, мы его не просим) трудно сказать со 100% гарантией того, что произошло. Но есть 2 варианта:
1) Ваш restartMachine
класс остановил машину, на которой это приложение работало само. Это вызвало JVM
останавливаясь с таким продолжением
2) Некоторые, где в вашем приложении вы запускаете упомянутый код в другом потоке, который где-то был остановлен способом, который я описал, или другим.
Таким образом, вы должны проанализировать эти пути и понять, что может быть более похожим на вашу ситуацию.
UPD: просто еще одна идея, 3) если вы запускаете приложение под Tomcat
например, это также может привести к такой проблеме, когда Tomcat
останавливает ваше приложение