Что такое FixedThreadPool в Java и когда его следует использовать?

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

Ответ

FixedThreadPool — это реализация пула потоков (ExecutorService) с фиксированным количеством рабочих потоков. Создаётся с помощью фабричного метода Executors.newFixedThreadPool(int nThreads).

Принцип работы:

  • Пул создаёт указанное количество потоков (nThreads) при инициализации.
  • Для хранения задач, которые не могут быть выполнены сразу (все потоки заняты), используется неограниченная очередь LinkedBlockingQueue.
  • Если поток завершается из-за непойманного исключения, пул создаёт новый поток для поддержания заданного количества.

Пример создания и использования:

import java.util.concurrent.*;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // Создаём пул из 4 потоков
        ExecutorService executor = Executors.newFixedThreadPool(4);

        // Отправляем 10 задач на выполнение
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Выполняется задача " + taskId + 
                                   " в потоке " + Thread.currentThread().getName());
                // Имитация работы
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
            });
        }

        // Инициируем упорядоченное завершение: выполняем все отправленные задачи, но не принимаем новые
        executor.shutdown();
        try {
            // Ожидаем завершения всех задач, но не более 1 минуты
            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                executor.shutdownNow(); // Принудительное завершение
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
    }
}

Когда использовать FixedThreadPool:

  • CPU-bound задачи: Когда задачи в основном вычислительные (не ждут I/O). Оптимальное количество потоков обычно равно количеству доступных ядер процессора (Runtime.getRuntime().availableProcessors()).
  • Когда нужно ограничить параллелизм: Чтобы не перегрузить систему или внешний ресурс (например, базу данных) большим количеством одновременных запросов.

Потенциальные проблемы:

  • Утечка памяти (OOM): Поскольку очередь неограничена, при быстром поступлении задач и медленном их выполнении очередь может бесконечно расти, потребляя всю память.
  • Взаимоблокировка (Deadlock): Если все потоки ожидают результаты задач, которые находятся в очереди, но не могут начать выполнение из-за отсутствия свободных потоков.
  • Простой потоков при I/O: Если задачи связаны с ожиданием I/O (сеть, диск), потоки будут простаивать, что неэффективно. В таких случаях лучше использовать CachedThreadPool или WorkStealingPool.