Как правильно отключить Java ExecutorService

У меня простая ява 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() в конце моей программы, но это глупо.

2 ответа

Рекомендуемый путь со страницы документации 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))

в

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

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

выключение ():

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

shutdownNow ():

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

awaitTermination (long timeout, TimeUnit unit) генерирует InterruptedException:

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

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

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

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

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

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