Java Threads и Shutdown Hook

Я только что столкнулся с интересной проблемой. Кажется, что если в Java поток вызывает System.exit(), он не может быть присоединен через Thread.join().

Это вызывает у меня проблемы, так как я хочу использовать перехватчик выключения для очистки после моего приложения, например:

Runtime.getRuntime.addShutdownHook(new Thread() {
    public void run() {
        reader.join();
        writer.join();
        in.close();
        out.close();
    }
});

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

  1. Пользователь нажимает [ctrl] + [C].
  2. Поток чтения завершается и вызывает System.exit().
  3. Поток записи завершается и вызывает System.exit().

Первый случай, когда пользователь нажимает [ctrl] + [C], работает нормально. Но в любом из двух других случаев хук выключения блокируется навсегда. Это косвенный эффект того факта, что один Thread.join(), который вызывается для потока, уже вызвавшего System.exit(), блокируется навсегда.

Таким образом, у меня есть 2 вопроса. Во-первых, я знаю, что вместо этого я мог бы использовать Thread.join(long millis), чтобы они не блокировались на неопределенный срок, но может ли кто-нибудь придумать более элегантное решение? Во-вторых, хотя можно дважды вызвать Thread.join() для одного и того же потока, и во втором случае он просто немедленно вернется, кто-нибудь знает причину, по которой вызов Thread.join() для потока, который уже вызвал System.exit(), блокируется на неопределенный срок, а не просто возвращается сразу?


person greydamian    schedule 18.07.2013    source источник
comment
Почему бы не попытаться избежать вызова System.exit() и вместо этого организовать надлежащую очистку и позволить JVM завершиться естественным образом?   -  person assylias    schedule 18.07.2013
comment
Также обратите внимание, что если System.exit() вызывается после того, как виртуальная машина начала свою последовательность завершения работы, то, если выполняются перехватчики выключения, этот метод будет блокироваться на неопределенный срок. - вы случайно не вызываете System.exit() дважды? (Справочник )   -  person assylias    schedule 18.07.2013


Ответы (4)


System.exit в случае успеха не возвращается даже через создание исключения, поэтому поток никогда не завершится. Это не было проблемой до перехватчиков выключения.

Обходной путь - обычные стандартные блокировки (или даже просто взломать его с помощью new Thread(new Runnable() { public void run() { System.exit(0); }}).start();).

person Tom Hawtin - tackline    schedule 18.07.2013

Вы делаете следующее: конкретно запрещено:

Отключающие хуки также должны быстро заканчивать свою работу.

Присоединение к случайным потокам нельзя назвать «быстрым».

Очевидно, что эти потоки должны закрывать свои собственные потоки при выходе. Если они не завершат работу, как вызов join() что-то добьется?

person user207421    schedule 19.07.2013
comment
Спасибо за ваш ответ. Извиняюсь, чтобы попытаться изложить мой вопрос кратко и по существу, я, очевидно, пропустил довольно много кода. На самом деле я сначала вызываю метод для потоков, чтобы сказать им, что они должны закончить как можно скорее, прежде чем я вызову Thead.join()s. К сожалению, у потоков нет независимых ресурсов. Таким образом, они не могут закрыть свои собственные ресурсы, если другой поток все еще использует их. - person greydamian; 19.07.2013

Не рекомендуется использовать System.exit(), так как это просто завершает работу JVM. Существуют и другие инструменты параллелизма, такие как CountDownLatch. Вы даже можете использовать finally для правильной очистки при завершении работы.

person Joshua    schedule 18.07.2013

Используйте исполнителей и не используйте объединение или создание потоков вручную.

При использовании исполнителя и других вещей в java.util.concurrent вы можете просто вызвать shutdown и дождаться его завершения, указать тайм-аут и т. д. Гораздо надежнее. Если вы используете java 1.7, используйте try с ресурсами, чтобы закрыть свои потоки. Таким образом, вам не нужно делать много работы по очистке, кроме как выключить исполнителя. Если нет, используйте блок finally. Никогда не вызывайте close нигде, кроме блока finally.

person Jilles van Gurp    schedule 18.07.2013