Как правильно отключить 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))
Краткое описание методов, связанных с отключением
Инициирует упорядоченное завершение, при котором выполняются ранее отправленные задачи, но новые задачи не принимаются.
Пытается остановить все активно выполняющиеся задачи, останавливает обработку ожидающих задач и возвращает список задач, ожидающих выполнения.
awaitTermination (long timeout, TimeUnit unit) генерирует InterruptedException:
Блокируется до тех пор, пока все задачи не завершат выполнение после запроса на выключение, или не истечет время ожидания, или текущий поток не прервется, в зависимости от того, что произойдет раньше.
У вас есть контроль над этими задачами? то есть ты сам их создаешь? Я подозреваю, что где-то в этих прерываниях потока игнорируется, например,
try {
....
}
catch {InterruptedException e) {
// do nothing
}
При возникновении InterruptedException флаг прерывания в потоке необходимо сбросить, иначе поток не выйдет. Смотрите здесь для получения дополнительной информации.
К сожалению, вы можете использовать библиотеку, которая не подчиняется этому, и в этом случае вы не можете легко обойти это. В этом случае один тяжелый вариант состоит в том, чтобы отключить подпроцесс для выполнения работы Callable
, и это очистит все ресурсы при выходе из процесса. Тяжелый и, возможно, нетривиальный, но надежный.