Ожидание завершения всех задач ExecutorService в Java

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Чтобы сделать ожидание завершения задач в ExecutorService управляемым, следует прекратить прием новых задач с помощью shutdown(), а затем использовать awaitTermination(), указав достаточное время для завершения всех операций.

Java
Скопировать код
ExecutorService executor = Executors.newFixedThreadPool(10);
// Добавляем задачи...
executor.shutdown(); // Прекращаем прием новых задач

// Ожидаем завершения всех задач до конца времен!
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

В работе ExecutorService существует следующий принцип:

  • ExecutorService организует очередь задач.
  • shutdown() говорит "Новые задачи больше не принимаются!"
  • awaitTermination() означает "Подождем, пока все задачи не будут выполнены".

Используйте Callable и метод invokeAll(), чтобы получать результаты выполнения задач. Этот метод дождется выполнения всех задач и вернет коллекцию объектов Future, содержащих результаты.

Java
Скопировать код
ExecutorService executor = Executors.newCachedThreadPool();
List<Callable<Object>> tasks = // ...

// Выполняем задачи и получаем результаты
List<Future<Object>> futures = executor.invokeAll(tasks);

// Обрабатываем результаты
for (Future<Object> future : futures) {
    // Получаем и используем результаты
}

Если вам необходима более эффективная обработка результатов, можно использовать CompletionService. Он позволяет извлекать результаты выполненных задач по мере их готовности.

Java
Скопировать код
ExecutorCompletionService<Object> completionService =
        new ExecutorCompletionService<>(executor);

// Подаём задачи на выполнение
tasks.forEach(completionService::submit);

int received = 0;
while (received < tasks.size()) {
    // Ожидаем выполнение всех задач
    Future<Object> resultFuture = completionService.take(); 
    received++;
}
Кинга Идем в IT: пошаговый план для смены профессии

Поглубже о синхронизации

invokeAll() — ваш менеджер выполнения задач

Метод invokeAll() принимает коллекцию задач Callable и возвращает список объектов Future после выполнения всех задач.

Избегаем неожиданностей: обработка исключений

Всегда готовьтесь к перехвату исключений: выполнение задачи может привести к ошибке. Ошибку вернет Future.get() в виде ExecutionException.

Один в поле не воин: обработка прерываний

В процессе долгого ожидания завершения задач обработайте InterruptedException, которое свидетельствует о том, что ваше ожидание было прервано.

Освоение управления ресурсами

Оптимальный размер пула потоков

Чтобы определить оптимальное количество потоков, начните с использования Runtime.availableProcessors().

Java
Скопировать код
int availableProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(availableProcessors);

Повторное использование ExecutorService

ExecutorService предназначен для повторного использования, поэтому не торопитесь его выключать командой shutdown() до тех пор, пока он не выполнит все задачи.

Элегантное завершение работы

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

Java
Скопировать код
executor.shutdown();
while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
    // Ожидаем выполнение всех задач.
}

Визуализация

Можно представить себе ExecutorService в виде шеф-повара 👨‍🍳, а задачи — как блюда 🥘 на кухне.

Markdown
Скопировать код
ExecutorService kitchenStaff = 🍳;

// Распределяем задачи между поварах
kitchenStaff.execute(salad);
kitchenStaff.execute(mainCourse);
kitchenStaff.execute(dessert);

kitchenStaff.shutdown(); // Приём заказов окончен.

while (!kitchenStaff.isTerminated()) {
    // Проверяем готовность всех блюд.
}

🎉 Всё готово! Приятного аппетита! 👏

Здесь shutdown() — это сигнал для поваров о том, что новые заказы больше не принимаются, а awaitTermination() убеждается, что все блюда готовы к подаче.

Полезные материалы

  1. ExecutorService (Java Platform SE 8)
  2. Документация JDK 21 — Главная
  3. CountDownLatch в Java – GeeksforGeeks
  4. Java – Как дождаться выполнения всех потоков с помощью ExecutorService? – Stack Overflow
  5. Многопоточность — Обработка исключений из задач Java ExecutorService – Stack Overflow