ExecutorService
Тема дорожной карты · Java
Java Executor Framework, введённый в Java 5 через java.util.concurrent, отделяет отправку задач от механики создания и управления потоками, заменяя прямые вызовы new Thread(runnable).start() чистой, основанной на пуле абстракцией. Центральный интерфейс ExecutorService расширяет Executor методами жизненного цикла (shutdown(), shutdownNow(), awaitTermination()) и методами отправки задач — submit(Callable<T>) возвращает Future<T> для отслеживания завершения и получения результатов, тогда как invokeAll() и invokeAny() обрабатывают пакеты задач Callable. Фабричный класс Executors предоставляет стандартные реализации пулов: newFixedThreadPool(n) создаёт пул ровно из n потоков с LinkedBlockingQueue, newCachedThreadPool() создаёт потоки по требованию и повторно использует простаивающие, newSingleThreadExecutor() гарантирует последовательное выполнение, а newScheduledThreadPool(n) поддерживает отложенные и периодические задачи через ScheduledExecutorService. ForkJoinPool, также часть Java Executor Framework, использует алгоритм похищения работы, оптимизированный для рекурсивных задач «разделяй и властвуй», и является пулом за CompletableFuture.supplyAsync() при отсутствии пользовательского executor. Корректное завершение работы ExecutorService и подбор размера пула потоков относительно доступных ядер CPU и соотношения ожидания ввода-вывода — критически важный навык Java Concurrency: слишком большие пулы расходуют кучу и вызывают накладные расходы на переключение контекста, а слишком маленькие создают задержку очереди.
Как это работает
ExecutorService имеет много инструментов: Thread (низкоуровневый), Executor / ExecutorService (пулы потоков), CompletableFuture (async-композиция), Future, java.util.concurrent коллекции (ConcurrentHashMap, BlockingQueue, CopyOnWriteArrayList), атомарные примитивы (AtomicInteger, AtomicReference). Java 21 даёт virtual threads (Project Loom — миллионы лёгких потоков, идеально для I/O-bound). Синхронизация: synchronized, ReentrantLock, ReadWriteLock, StampedLock. Memory model (JMM) управляет видимостью.
Когда применять
ExecutorService вместо сырых Thread — пулы управляют жизненным циклом. CompletableFuture — для async-композиции (thenApply, thenCompose, allOf). На Java 21+ virtual threads делают блокирующий I/O практичным на высокой конкурентности (Executors.newVirtualThreadPerTaskExecutor()). ConcurrentHashMap — для shared-state. Прочтите "Java Concurrency in Practice" до серьёзного concurrent-кода.
Типичные ошибки
Ловушки ExecutorService: shared mutable state без синхронизации (visibility-баги); volatile-"фикс" для race, требующих атомарности (volatile только видимость, не атомарный RMW); deadlock из-за двух локов в разном порядке; не daemon-threads когда нужно (JVM висит при shutdown); слишком много потоков для I/O-bound до Loom (новый ответ — virtual threads).