Использовали ли ForkJoinPool на практике
Что проверяют
Понимаете ли вы, где ForkJoinPool работает у вас в коде даже без явного вызова, и чем он отличается от обычного ThreadPoolExecutor. Следом спрашивают про work stealing и почему в общем пуле нельзя делать блокирующий ввод-вывод.
Как отвечать
Сначала механика. ForkJoinPool рассчитан на задачи «разделяй и властвуй»: задача делит работу пополам, одну часть отдаёт в fork(), другую считает сама и собирает результат через join().
RecursiveTask<V>: задача с результатом, например сумма по массиву;RecursiveAction: задача без результата, например обработка элементов на месте;- work stealing: у каждого потока своя двусторонняя очередь. Свои задачи он берёт с одного конца, а простаивающий поток крадёт с другого, поэтому ядра загружены даже на неравномерной работе;
- порог: ниже определённого размера задача считается последовательно, иначе накладные расходы на подзадачи съедят выигрыш.
Потом где он встречается на практике. ForkJoinPool.commonPool() используют parallelStream() и CompletableFuture.supplyAsync() без явного executor. Его параллелизм по умолчанию на единицу меньше числа процессоров и настраивается свойством java.util.concurrent.ForkJoinPool.common.parallelism. Виртуальные потоки в Java 21 тоже работают поверх ForkJoinPool, только отдельного, выделенного под планировщик.
Пример из практики должен быть вашим: что считали, какой объём данных, какой порог выбрали, как мерили (JMH или хотя бы замеры на реальных данных). Если явный ForkJoinPool вы не писали, это нормально, так и скажите. Расскажите, где сталкивались с ним косвенно через parallelStream() или CompletableFuture, и какие выводы сделали.
Где спотыкаются
Отправляют в commonPool запросы к базе или HTTP. Потоки блокируются, и тормозят все параллельные стримы приложения. Для ввода-вывода нужен свой executor или виртуальные потоки.
Вызывают join() сразу после fork(), вместо того чтобы посчитать вторую половину в текущем потоке. Параллелизм пропадает.
Называют ускорение без замеров. На маленьких коллекциях parallelStream() бывает медленнее последовательного.