ForkJoinPool: когда одна задача делится на тысячи мелких
Обычный пул потоков просто выполняет задачи по очереди.
ForkJoinPool умнее: он создан для стратегии «разделяй и властвуй».
Как это работает:
Если у одного потока работы не осталось, он не спит — он «крадёт» задачу у соседа. Поэтому ядра процессора всегда заняты делом.
Когда использовать:
* CPU-bound задачи: сложные вычисления, сортировка больших массивов, обработка деревьев или графов.
* Когда одну большую задачу можно разбить на сотни одинаковых маленьких подзадач.
Как выглядит в коде:
Задача должна наследоваться от RecursiveTask<V> (если нужен результат) или RecursiveAction (если нет).
Внутри метода compute() вы либо считаете всё сразу, либо делите задачу пополам:
task.fork(); // Запустили часть асинхронно
result = otherPart.compute(); // Считаем вторую часть сами
result += task.join(); // Дождались первой части и забрали ответ
Главное правило Java-разработчика:
Никогда не делайте блокирующие операции (запросы к БД, чтение файлов, сетевые вызовы) внутри задач ForkJoinPool. Пул быстро опустеет, потоки уснут в ожидании I/O, а ворк-стилинг перестанет работать. Для ввода-вывода используйте обычный ThreadPoolExecutor.
Для обычных стримов (parallelStream()) трогать его руками не нужно — там уже работает общий системный пул (commonPool). Свою фабрику стоит создавать только для тяжелых вычислительных узлов.