Что такое концепция ForkJoin
Что проверяют
Понимаете ли вы, чем пул с воровством задач отличается от обычного и когда рекурсивное деление окупается. Заодно проверяют, знаете ли вы, что на этом же пуле работают параллельные потоки данных, то есть вы им пользуетесь, даже когда не пишете задачи руками.
Как отвечать
Идея в трёх словах: раздели, посчитай параллельно, объедини. Задача смотрит на размер своего куска работы; если он велик, она делится на подзадачи, иначе считает напрямую. Подзадачи попадают в ForkJoinPool, где у каждого рабочего потока своя очередь, а простаивающий поток забирает работу с хвоста чужой. Это и есть воровство задач: нагрузка выравнивается без общей блокировки.
Наследуются от RecursiveTask, когда нужен результат, и от RecursiveAction, когда нет.
protected Long compute() {
if (hi - lo <= THRESHOLD) return sumDirectly();
int mid = lo + (hi - lo) / 2;
Sum left = new Sum(a, lo, mid);
left.fork(); // отдали половину в пул
long right = new Sum(a, mid, hi).compute();
return right + left.join(); // вторую посчитали сами
}Порог подбирают замером: слишком мелкие куски съедаются накладными расходами на постановку и снятие задач.
Где спотыкаются
Зовут fork на обе половины, а потом join на обе. Текущий поток при этом просто ждёт вместо того, чтобы считать вторую половину самому, и выигрыш от параллелизма тает.
Блокируются внутри задачи: ходят в сеть, в базу, ждут на замке. Общий пул невелик по числу потоков, и несколько таких задач останавливают заодно все параллельные потоки данных в приложении. Для ожидания есть ManagedBlocker или отдельный пул.
Берут этот механизм для любой параллельной работы. Он рассчитан на счётные задачи, которые дробятся рекурсивно и не трогают общее изменяемое состояние, а для разнородных независимых заданий проще обычный пул исполнителей.