Ответ
ExecutorService — это интерфейс фреймворка java.util.concurrent, представляющий асинхронный механизм выполнения задач в пуле потоков. Он абстрагирует ручное создание и управление потоками (Thread), предоставляя высокоуровневый API для управления жизненным циклом задач.
Ключевые методы:
execute(Runnable task): Запускает задачу без возможности получить результат.submit(Callable<T> task)/submit(Runnable task, T result): Отправляет задачу на выполнение и возвращаетFuture<T>для отслеживания статуса и получения результата.invokeAll(Collection<Callable<T>> tasks): Запускает все задачи и возвращает списокFutureобъектов, когда все завершатся.invokeAny(Collection<Callable<T>> tasks): Запускает все задачи и возвращает результат первой успешно завершившейся.shutdown(): Инициирует упорядоченное завершение, при котором выполняются ранее отправленные задачи, но новые задачи не принимаются.shutdownNow(): Пытается остановить все активно выполняемые задачи, останавливает обработку ожидающих задач и возвращает список задач, которые не были выполнены.awaitTermination(long timeout, TimeUnit unit): Блокирует поток до завершения всех задач после вызоваshutdown()или истечения таймаута.
Создание пулов через Executors (фабричные методы):
// 1. Фиксированный пул потоков
ExecutorService fixedPool = Executors.newFixedThreadPool(10); // 10 рабочих потоков
// 2. Пул с кэшированием потоков (создает новые при необходимости)
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 3. Один поток (последовательное выполнение)
ExecutorService singleThread = Executors.newSingleThreadExecutor();
// 4. Пул для планирования задач
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(4);
scheduledPool.schedule(() -> System.out.println("Запуск через 5 сек"), 5, TimeUnit.SECONDS);
scheduledPool.scheduleAtFixedRate(() -> System.out.println("Повтор каждые 2 сек"), 1, 2, TimeUnit.SECONDS);
Правильное использование и завершение (шаблон try-with-resources с Java 19+):
// Рекомендуемый способ с Java 19+ (AutoCloseable)
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { // или любой другой пул
Future<Integer> future = executor.submit(() -> {
// Длительная задача
Thread.sleep(1000);
return 42;
});
// Блокирующее получение результата с таймаутом
Integer result = future.get(2, TimeUnit.SECONDS);
System.out.println("Результат: " + result);
} // executor.shutdown() вызывается автоматически
catch (TimeoutException e) {
System.err.println("Задача не завершилась вовремя");
}
catch (Exception e) {
e.printStackTrace();
}
// Классический способ (до Java 19)
ExecutorService executor = Executors.newFixedThreadPool(4);
try {
// Отправка задач...
List<Future<String>> futures = executor.invokeAll(listOfCallables);
for (Future<String> f : futures) {
System.out.println(f.get());
}
} finally {
executor.shutdown(); // Начинаем завершение
try {
// Ждем завершения всех задач, но не более 1 часа
if (!executor.awaitTermination(1, TimeUnit.HOURS)) {
executor.shutdownNow(); // Принудительная остановка
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt(); // Восстанавливаем флаг прерывания
}
}
Тонкая настройка через ThreadPoolExecutor:
// Позволяет точно контролировать параметры пула
int corePoolSize = 5; // Минимальное количество потоков
int maxPoolSize = 20; // Максимальное количество потоков
long keepAliveTime = 60L; // Время простоя лишних потоков
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // Очередь задач
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); // Политика при переполнении
ExecutorService customPool = new ThreadPoolExecutor(
corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler
);
Политики обработки переполнения очереди (RejectedExecutionHandler):
AbortPolicy(по умолчанию): ВыбрасываетRejectedExecutionException.CallerRunsPolicy: Выполняет задачу в потоке, который вызвалexecute/submit.DiscardPolicy: Молча отбрасывает задачу.DiscardOldestPolicy: Удаляет самую старую задачу из очереди и пытается добавить новую.
Лучшие практики:
- Всегда завершайте
ExecutorServiceс помощьюshutdown()/awaitTermination(). - Используйте
CallableиFutureдля получения результатов и обработки исключений. - Настройте размер пула в зависимости от типа задач (CPU-bound vs I/O-bound).
- Обрабатывайте
RejectedExecutionExceptionпри высокой нагрузке. - Рассмотрите
CompletableFuture(Java 8+) для более сложных асинхронных цепочек и комбинаций.