Как правильно закрыть java ExecutorService

У меня есть простой java ExecutorService, который запускает некоторые объекты задачи (реализует Callable).

ExecutorService exec = Executors.newSingleThreadExecutor();
List<CallableTask> tasks = new ArrayList<>();
// ... create some tasks
for (CallableTask task : tasks) {
 Future future = exec.submit(task);
 result = (String) future.get(timeout, TimeUnit.SECONDS);
 // TASKS load some classes and invoke their methods (they may create additional threads)
 // ... catch interruptions and timeouts
}
exec.shutdownNow();

После завершения всех задач (DONE или TIMEOUT-ed) я пытаюсь выключить исполнитель, но он не остановится: exec.isTerminated() = FALSE. Я подозреваю, что некоторые выполняемые задачи не завершены должным образом.

И да, я знаю, что выключение исполнителя не гарантирует ничего:

Нет никаких гарантий, кроме попыток прекращения усилий       обработка, активно выполняющая задачи. Например, типичный       реализация будет отменена с помощью {@link Thread # interrupt}, поэтому любые      задача, которая не отвечает на прерывания, никогда не может завершиться.

Мой вопрос в том, есть ли способ гарантировать, что потоки (задачи) прекратятся? Лучшее решение, которое я придумал, - это вызвать System.exit() в конце моей программы, но это глупо.

Ответ 1

Рекомендуемый способ на странице документации Oracle API ExecutorService:

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }

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

1f (!pool.awaitTermination(60, TimeUnit.SECONDS))

to

while (!pool.awaitTermination(60, TimeUnit.SECONDS))

Краткий обзор связанных с выключением методов

shutdown():

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

shutdownNow():

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

waitTermination (длительный тайм-аут, единица TimeUnit) выбрасывает InterruptedException:

Блокирует до тех пор, пока все задачи не завершили выполнение после запроса на завершение работы или не произойдет тайм-аут, или текущий поток не будет прерван, в зависимости от того, что произойдет раньше.

Ответ 2

У вас есть контроль над этими задачами? т.е. вы сами создаете их? Я подозреваю, что в тех случаях, когда прерывание потока игнорируется, например.

try {
  ....
}
catch {InterruptedException e) {
   // do nothing
}

При вызове InterruptedException флаг прерывания в потоке должен быть reset, иначе поток не выйдет. Подробнее см. .

К сожалению, вы можете использовать библиотеку, которая не подчиняется этому, и в этом случае вы не можете легко обойти это. В этом случае одна опция супертяга состоит в том, чтобы разблокировать вспомогательный процесс для выполнения задания Callable, и это очистит все ресурсы после выхода из процесса. Супертяжелый вес и, возможно, нетривиальный, но надежный.