Ожидание завершения всех задач ExecutorService в Java
Быстрый ответ
Чтобы сделать ожидание завершения задач в ExecutorService
управляемым, следует прекратить прием новых задач с помощью shutdown()
, а затем использовать awaitTermination()
, указав достаточное время для завершения всех операций.
ExecutorService executor = Executors.newFixedThreadPool(10);
// Добавляем задачи...
executor.shutdown(); // Прекращаем прием новых задач
// Ожидаем завершения всех задач до конца времен!
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
В работе ExecutorService существует следующий принцип:
- ExecutorService организует очередь задач.
- shutdown() говорит "Новые задачи больше не принимаются!"
- awaitTermination() означает "Подождем, пока все задачи не будут выполнены".
Используйте Callable
и метод invokeAll()
, чтобы получать результаты выполнения задач. Этот метод дождется выполнения всех задач и вернет коллекцию объектов Future
, содержащих результаты.
ExecutorService executor = Executors.newCachedThreadPool();
List<Callable<Object>> tasks = // ...
// Выполняем задачи и получаем результаты
List<Future<Object>> futures = executor.invokeAll(tasks);
// Обрабатываем результаты
for (Future<Object> future : futures) {
// Получаем и используем результаты
}
Если вам необходима более эффективная обработка результатов, можно использовать CompletionService. Он позволяет извлекать результаты выполненных задач по мере их готовности.
ExecutorCompletionService<Object> completionService =
new ExecutorCompletionService<>(executor);
// Подаём задачи на выполнение
tasks.forEach(completionService::submit);
int received = 0;
while (received < tasks.size()) {
// Ожидаем выполнение всех задач
Future<Object> resultFuture = completionService.take();
received++;
}
Поглубже о синхронизации
invokeAll() — ваш менеджер выполнения задач
Метод invokeAll()
принимает коллекцию задач Callable
и возвращает список объектов Future
после выполнения всех задач.
Избегаем неожиданностей: обработка исключений
Всегда готовьтесь к перехвату исключений: выполнение задачи может привести к ошибке. Ошибку вернет Future.get()
в виде ExecutionException
.
Один в поле не воин: обработка прерываний
В процессе долгого ожидания завершения задач обработайте InterruptedException
, которое свидетельствует о том, что ваше ожидание было прервано.
Освоение управления ресурсами
Оптимальный размер пула потоков
Чтобы определить оптимальное количество потоков, начните с использования Runtime.availableProcessors()
.
int availableProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(availableProcessors);
Повторное использование ExecutorService
ExecutorService
предназначен для повторного использования, поэтому не торопитесь его выключать командой shutdown()
до тех пор, пока он не выполнит все задачи.
Элегантное завершение работы
Добейтесь корректного завершения работы сервиса вне зависимости от обстоятельств, организовав цикл, который будет регулярно проверять, не выполнились ли все задачи.
executor.shutdown();
while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
// Ожидаем выполнение всех задач.
}
Визуализация
Можно представить себе ExecutorService
в виде шеф-повара 👨🍳, а задачи — как блюда 🥘 на кухне.
ExecutorService kitchenStaff = 🍳;
// Распределяем задачи между поварах
kitchenStaff.execute(salad);
kitchenStaff.execute(mainCourse);
kitchenStaff.execute(dessert);
kitchenStaff.shutdown(); // Приём заказов окончен.
while (!kitchenStaff.isTerminated()) {
// Проверяем готовность всех блюд.
}
🎉 Всё готово! Приятного аппетита! 👏
Здесь shutdown()
— это сигнал для поваров о том, что новые заказы больше не принимаются, а awaitTermination()
убеждается, что все блюда готовы к подаче.