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 останавливает ваше приложение

Другие вопросы по тегам