IllegalMonitorStateException on awaitTermination function

У меня проблема с использованием потоков в Java (у меня мало опыта работы с потоками в Java, но многое в С++, поэтому я понимаю базовую концепцию потоков). Я использовал пример кода для потоков в 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, и нет другого кода, касающегося потоков в программе (поэтому я в основном использую только код из библиотеки)

Я был бы признателен за любую помощь, я знаю, что на эту проблему уже задано много вопросов (это исключение), но я не знаю, в чем проблема.

Ответ 1

Эта проблема может быть легко воспроизведена, если мы обернем ваш код внутри некоторого 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% waranty произошло. Но есть две возможности:

1) Ваш класс restartMachine остановил машину, на которой выполнялось это приложение. Это привело к остановке JVM с таким продолжением

2) Некоторые, где в вашем приложении вы запустили упомянутый код в другом потоке, который где-то был остановлен так, как я описал, или другой.

Итак, вам нужно проанализировать эти способы и понять, что может быть более похоже на вашу ситуацию.

UPD. Еще одна идея: 3) если вы используете приложение под Tomcat, например, это также может привести к такой проблеме, когда Tomcat останавливает ваше приложение.

Ответ 2

Это очень странно и, вероятно, не ваша вина:

Javadoc of 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: [email protected][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 stackoverflow.Test$1.run(Test.java:24)
    at java.lang.Thread.run(Thread.java:745)

...

[email protected][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, а воздействие довольно незначительное (неправильный тип исключения вызывается), они могут его не исправлять.