Ответ
Fork/Join — это фреймворк в Java (пакет java.util.concurrent), предназначенный для эффективного параллельного выполнения рекурсивных задач, которые можно разбивать на подзадачи (divide-and-conquer). Он реализован на основе work-stealing алгоритма.
Ключевые компоненты:
- ForkJoinPool: Специальный пул потоков-исполнителей. Потоки в нем могут "красть" задачи из очередей других потоков, что обеспечивает балансировку нагрузки.
- ForkJoinTask: Абстрактный базовый класс для задач. На практике используются его подклассы:
RecursiveAction— для задач, не возвращающих результат.RecursiveTask<V>— для задач, возвращающих результат.
Принцип работы:
- Задача проверяет, достаточно ли она мала для непосредственного выполнения (базовый случай).
- Если нет, она разделяется (fork) на две или более подзадач, которые асинхронно запускаются в пуле.
- Подзадачи рекурсивно повторяют шаги 1-2.
- Результаты подзадаж объединяются (join).
Пример: вычисление суммы элементов массива с помощью RecursiveTask:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
class SumTask extends RecursiveTask<Long> {
private final int[] array;
private final int start, end;
private static final int THRESHOLD = 10_000; // Порог для прямого вычисления
SumTask(int[] array, int start, int end) {
this.array = array; this.start = start; this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) { // Базовый случай
long sum = 0;
for (int i = start; i < end; i++) sum += array[i];
return sum;
} else { // Разделение задачи
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork(); // Асинхронный запуск левой подзадачи
long rightResult = right.compute(); // Синхронное вычисление правой
long leftResult = left.join(); // Ожидание результата левой
return leftResult + rightResult; // Объединение результатов
}
}
}
// Использование
int[] data = new int[100_000];
ForkJoinPool pool = new ForkJoinPool();
long result = pool.invoke(new SumTask(data, 0, data.length));
Почему Fork/Join эффективен? Work-stealing минимизирует время простоя потоков, что делает фреймворк идеальным для задач с высокой степенью рекурсивного параллелизма.