ExecutorService

Создавать новый поток на каждую задачу дорого. Пул потоков (ExecutorService) держит набор готовых потоков и раздаёт им задачи:

ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doWork());
pool.shutdown();   // важно остановить пул, когда задачи закончились

Пул переиспользует потоки вместо создания новых на каждый вызов, а размер пула ограничивает, сколько задач выполняется параллельно — остальные ждут очередь.

Копнуть глубже

Виды пулов из Executors:

Фабричный методЧто делает
newFixedThreadPool(n)ровно n потоков, остальные в очереди
newCachedThreadPool()поток на каждую задачу; лишние потоки умирают через 60с
newSingleThreadExecutor()один поток, задачи выполняются по очереди
newScheduledThreadPool(n)для задач с задержкой или по расписанию

Как выбрать размер пула. Нет универсального ответа, зависит от задачи:

  • CPU-intensive задачи (вычисления): n = CPU_cores или CPU_cores + 1
  • IO-intensive (HTTP, БД): потоки большую часть времени ждут — можно 10–50 × CPU_cores

ThreadPoolExecutor напрямую — когда нужна тонкая настройка:

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    4,              // corePoolSize — постоянные потоки
    16,             // maximumPoolSize — максимум при нагрузке
    60, TimeUnit.SECONDS,   // keepAlive для лишних потоков
    new LinkedBlockingQueue<>(1000)  // очередь задач (ограниченная!)
);
// Если очередь переполнена — политика отказа
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
🎤 Закрыл тему, если можешь объяснить:
• зачем нужен пул потоков и чем он лучше создания `new Thread()` на каждую задачу;
• чем отличаются fixed vs cached пулы и как выбрать размер (если дошёл до 2-го слоя).