В чем суть метода fork/join и как он работает с потоками в Java
Что проверяют
Знаете ли вы, как устроен ForkJoinPool, а не только шаблон «разделил и собрал». Типичное продолжение: где этот пул используется неявно и почему в нём нельзя блокироваться.
Как отвечать
Fork/Join это не метод, а фреймворк из java.util.concurrent (с Java 7) для задач, которые рекурсивно делятся на независимые части:
- задачи:
RecursiveTask<V>возвращает результат,RecursiveActionнет. Вcompute()задача либо решает кусок сама, если он меньше порога, либо делится; - fork и join:
fork()кладёт подзадачу в очередь текущего потока,join()ждёт результат. Классический приём:left.fork(), затемright.compute()в текущем потоке, затемleft.join(). Так поток не простаивает; - work stealing: у каждого рабочего потока своя двусторонняя очередь. Свои задачи он берёт с одного конца, а свободный поток крадёт из чужой очереди с противоположного. Поток, ждущий в
join(), не спит, а выполняет другие задачи; - общий пул:
ForkJoinPool.commonPool()с параллелизмом по умолчанию на единицу меньше числа ядер. На нём работают параллельные стримы иCompletableFuture.supplyAsync()без явного executor.
Порог подбирают замерами, обычно это тысячи элементов, а не десяток: создание задачи тоже стоит денег. Мерить стоит через JMH, а не System.currentTimeMillis().
Где спотыкаются
Делают блокирующий ввод-вывод в задачах общего пула. Потоков в нём мало, и медленный HTTP-вызов внутри parallelStream() тормозит все параллельные стримы приложения. Для блокирующих операций есть ForkJoinPool.ManagedBlocker или отдельный пул.
Форкают обе половины и соединяют в неудачном порядке. Документация советует делать join в обратном порядке, а ещё проще форкнуть одну половину и посчитать вторую в текущем потоке.
Считают, что fork/join ускорит любой код. На маленьких данных или задачах с общим изменяемым состоянием и синхронизацией параллельная версия выходит медленнее последовательной.