Зачем использовать ExecutorService для выполнения большого количества (например, 10000) задач?

«Зачем использовать ExecutorService для выполнения большого количества (например, 10000) задач?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

ExecutorService решает ключевую проблему «один поток на задачу»: создание 10000 потоков нативно (new Thread()) приведет к огромным накладным расходам на управление памятью и переключение контекста, что может исчерпать ресурсы системы и привести к ее зависанию.

Преимущества ExecutorService с пулом потоков:

  • Контроль ресурсов: Пул потоков фиксированного размера (например, 10 потоков) переиспользует существующие потоки для выполнения всех задач, избегая затрат на их создание и уничтожение.
  • Очередь задач: Задачи, которые не могут быть немедленно выполнены, помещаются в очередь (BlockingQueue) и обрабатываются по мере освобождения потоков.
  • Управление жизненным циклом: Предоставляет методы для плавной остановки (shutdown(), awaitTermination()) и принудительной отмены выполнения.
  • Получение результатов: Возвращает объекты Future<T>, позволяющие получить результат асинхронной задачи или исключение.

Пример обработки 10000 задач:

import java.util.concurrent.*;

public class TaskProcessor {
    private static final int TASK_COUNT = 10000;
    private static final int POOL_SIZE = 10;

    public static void main(String[] args) throws InterruptedException {
        // Создаем пул из 10 потоков
        ExecutorService executor = Executors.newFixedThreadPool(POOL_SIZE);

        for (int i = 0; i < TASK_COUNT; i++) {
            int taskId = i;
            executor.submit(() -> {
                System.out.println("Выполняется задача " + taskId + " в потоке " + Thread.currentThread().getName());
                // Логика задачи...
            });
        }

        // Инициируем плавное завершение: выполняем все задачи из очереди, но не принимаем новые.
        executor.shutdown();
        // Ожидаем завершения всех задач, но не более 1 часа.
        executor.awaitTermination(1, TimeUnit.HOURS);
    }
}

Такой подход обеспечивает высокую пропускную способность при ограниченном потреблении ресурсов.