В чем разница между ForkJoinPool и FixedThreadPool в Java?

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

Ответ

Оба являются реализациями ExecutorService, но используют принципиально разные алгоритмы планирования задач, оптимизированные под разные типы workloads.

FixedThreadPool

  • Архитектура: Создает пул с фиксированным количеством потоков. Задачи помещаются в общую очередь (обычно LinkedBlockingQueue).
  • Алгоритм планирования: Потоки берут задачи из очереди по принципу FIFO (First-In-First-Out).
  • Идеален для: Большого количества независимых, однородных по времени выполнения задач (например, обработка HTTP-запросов, параллельные вычисления без зависимостей).
ExecutorService fixedPool = Executors.newFixedThreadPool(4);
// 4 потока будут брать задачи из одной общей очереди.
fixedPool.submit(() -> System.out.println("Task 1"));
fixedPool.submit(() -> System.out.println("Task 2"));

ForkJoinPool

  • Архитектура: Реализует алгоритм work-stealing. У каждого рабочего потока есть своя двусторонняя очередь (deque) задач.
  • Алгоритм планирования: Поток выполняет задачи из головы своей очереди. Если его очередь пуста, он может "украсть" (steal) задачу из хвоста очереди другого потока.
  • Идеален для: Рекурсивных задач, которые можно эффективно разбивать на подзадачи (алгоритмы "разделяй и властвуй"), например:
    • Обход деревьев/графов
    • Параллельная сортировка (MergeSort)
    • Вычисления типа RecursiveTask
class FibonacciTask extends RecursiveTask<Long> {
    final long n;
    FibonacciTask(long n) { this.n = n; }
    protected Long compute() {
        if (n <= 1) return n;
        FibonacciTask f1 = new FibonacciTask(n - 1);
        f1.fork(); // Асинхронное выполнение подзадачи
        FibonacciTask f2 = new FibonacciTask(n - 2);
        return f2.compute() + f1.join(); // join() ожидает результат
    }
}

ForkJoinPool pool = ForkJoinPool.commonPool();
Long result = pool.invoke(new FibonacciTask(10));

Сравнительная таблица

Характеристика FixedThreadPool ForkJoinPool
Очередь Одна общая (LinkedBlockingQueue) Много деков (deque) по одной на поток (work-stealing)
Планирование FIFO из общей очереди Work-stealing из деков других потоков
Оптимизация Для независимых задач Для рекурсивных задач с разбиением (fork/join)
Риск блокировки Может возникнуть, если задачи блокирующие Меньше, благодаря work-stealing
Размер пула Задается явно Задается уровнем параллелизма (parallelism). По умолчанию = кол-во ядер CPU.

Вывод: Используйте FixedThreadPool для простых параллельных задач. Используйте ForkJoinPool (чаще через parallelStream() или CompletableFuture) для рекурсивных или легко распараллеливаемых вычислений, особенно когда задачи могут порождать подзадачи.