← Назад к вопросам

Что такое RecursiveTask?

2.7 Senior🔥 151 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое RecursiveTask

RecursiveTask — это абстрактный класс из Java framework Fork/Join, который используется для решения задач методом "разделяй и властвуй" (divide and conquer) с использованием параллелизма. RecursiveTask предназначена для задач, которые возвращают результат.

Это часть пакета java.util.concurrent и является основой для эффективной параллельной обработки в Java.

Основная идея Fork/Join

Оригинальная задача (размер: 1000000 элементов)
    |
    +---> Fork: разделяем на подзадачи
    |
    +---> Задача 1 (250000 элементов) → обрабатываем параллельно
    |     |
    |     +---> Fork: еще разделяем
    |     |     Задача 1a (125000) | Задача 1b (125000)
    |     |
    |     +---> Join: объединяем результаты
    |
    +---> Задача 2 (250000 элементов) → обрабатываем параллельно
    |
    +---> Задача 3 (250000 элементов) → обрабатываем параллельно
    |
    +---> Задача 4 (250000 элементов) → обрабатываем параллельно
    |
    +---> Join: собираем все результаты
    |
    v
Итоговый результат

Основная структура RecursiveTask

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

// RecursiveTask<V> - V это тип результата
public class MyTask extends RecursiveTask<Integer> {
    
    private int[] array;
    private int start;
    private int end;
    private static final int THRESHOLD = 1000;  // Порог для разделения
    
    public MyTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Integer compute() {
        // Если размер задачи достаточно маленький - вычисляем напрямую
        if (end - start <= THRESHOLD) {
            return computeDirectly();
        }
        
        // Иначе разделяем (fork) и объединяем (join)
        int mid = (start + end) / 2;
        
        MyTask leftTask = new MyTask(array, start, mid);
        MyTask rightTask = new MyTask(array, mid, end);
        
        leftTask.fork();   // Запускаем левую задачу в отдельном потоке
        int rightResult = rightTask.compute();  // Вычисляем правую задачу в текущем потоке
        int leftResult = leftTask.join();  // Ждем результата левой задачи
        
        return leftResult + rightResult;  // Объединяем результаты
    }
    
    private Integer computeDirectly() {
        int sum = 0;
        for (int i = start; i < end; i++) {
            sum += array[i];
        }
        return sum;
    }
}

// Использование
public class ForkJoinExample {
    public static void main(String[] args) {
        int[] array = new int[1_000_000];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }
        
        // Создаем пул Fork/Join потоков
        ForkJoinPool pool = new ForkJoinPool();
        
        // Создаем и запускаем задачу
        MyTask task = new MyTask(array, 0, array.length);
        Integer result = pool.invoke(task);
        
        System.out.println("Sum: " + result);
        pool.shutdown();
    }
}

Сравнение: RecursiveTask vs RecursiveAction

// RecursiveTask<V> - возвращает результат
public class SumTask extends RecursiveTask<Long> {
    @Override
    protected Long compute() {
        // Вычисляем и возвращаем результат
        return result;
    }
}

// RecursiveAction - не возвращает результат
public class PrintTask extends RecursiveAction {
    @Override
    protected void compute() {
        // Выполняем побочные эффекты (вывод, запись в файл)
    }
}

Реальные примеры

1. Сумма элементов массива

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

public class ArraySumTask extends RecursiveTask<Long> {
    private final long[] numbers;
    private final int start;
    private final int end;
    private static final int THRESHOLD = 1000;
    
    public ArraySumTask(long[] numbers, int start, int end) {
        this.numbers = numbers;
        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 += numbers[i];
            }
            return sum;
        }
        
        // Иначе разделяем
        int mid = (start + end) / 2;
        ArraySumTask leftTask = new ArraySumTask(numbers, start, mid);
        ArraySumTask rightTask = new ArraySumTask(numbers, mid, end);
        
        leftTask.fork();
        Long rightSum = rightTask.compute();
        Long leftSum = leftTask.join();
        
        return leftSum + rightSum;
    }
    
    public static void main(String[] args) {
        long[] numbers = new long[10_000_000];
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = i + 1;
        }
        
        ForkJoinPool pool = ForkJoinPool.commonPool();
        ArraySumTask task = new ArraySumTask(numbers, 0, numbers.length);
        Long result = pool.invoke(task);
        
        System.out.println("Sum: " + result);
    }
}

2. Поиск максимального значения в массиве

public class MaxValueTask extends RecursiveTask<Integer> {
    private int[] array;
    private int start;
    private int end;
    private static final int THRESHOLD = 5000;
    
    public MaxValueTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Integer compute() {
        if (end - start <= THRESHOLD) {
            // Базовый случай: вычисляем максимум
            int max = Integer.MIN_VALUE;
            for (int i = start; i < end; i++) {
                max = Math.max(max, array[i]);
            }
            return max;
        }
        
        // Рекурсивный случай
        int mid = (start + end) / 2;
        MaxValueTask leftTask = new MaxValueTask(array, start, mid);
        MaxValueTask rightTask = new MaxValueTask(array, mid, end);
        
        leftTask.fork();
        int rightMax = rightTask.compute();
        int leftMax = leftTask.join();
        
        return Math.max(leftMax, rightMax);
    }
    
    public static void main(String[] args) {
        int[] array = {3, 7, 2, 9, 1, 5, 8, 4, 6};
        
        ForkJoinPool pool = ForkJoinPool.commonPool();
        MaxValueTask task = new MaxValueTask(array, 0, array.length);
        Integer maxValue = pool.invoke(task);
        
        System.out.println("Max value: " + maxValue);
    }
}

3. Сортировка массива (Merge Sort)

public class MergeSortTask extends RecursiveTask<int[]> {
    private int[] array;
    private int start;
    private int end;
    private static final int THRESHOLD = 1000;
    
    public MergeSortTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected int[] compute() {
        // Если размер маленький - используем обычную сортировку
        if (end - start <= THRESHOLD) {
            int[] temp = new int[end - start];
            System.arraycopy(array, start, temp, 0, end - start);
            java.util.Arrays.sort(temp);
            return temp;
        }
        
        // Разделяем
        int mid = (start + end) / 2;
        MergeSortTask leftTask = new MergeSortTask(array, start, mid);
        MergeSortTask rightTask = new MergeSortTask(array, mid, end);
        
        leftTask.fork();
        int[] rightSorted = rightTask.compute();
        int[] leftSorted = leftTask.join();
        
        // Объединяем отсортированные части
        return merge(leftSorted, rightSorted);
    }
    
    private int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];
        int i = 0, j = 0, k = 0;
        
        while (i < left.length && j < right.length) {
            result[k++] = (left[i] <= right[j]) ? left[i++] : right[j++];
        }
        
        while (i < left.length) {
            result[k++] = left[i++];
        }
        
        while (j < right.length) {
            result[k++] = right[j++];
        }
        
        return result;
    }
}

4. Обход дерева и подсчет узлов

public class TreeNode {
    public int value;
    public TreeNode left;
    public TreeNode right;
    
    public TreeNode(int value) {
        this.value = value;
    }
}

public class TreeCountTask extends RecursiveTask<Integer> {
    private TreeNode node;
    
    public TreeCountTask(TreeNode node) {
        this.node = node;
    }
    
    @Override
    protected Integer compute() {
        // Базовый случай: листовой узел
        if (node == null) {
            return 0;
        }
        
        if (node.left == null && node.right == null) {
            return 1;
        }
        
        // Рекурсивно обрабатываем поддеревья
        TreeCountTask leftTask = null;
        TreeCountTask rightTask = null;
        
        if (node.left != null) {
            leftTask = new TreeCountTask(node.left);
            leftTask.fork();
        }
        
        if (node.right != null) {
            rightTask = new TreeCountTask(node.right);
            rightTask.fork();
        }
        
        int leftCount = (leftTask == null) ? 0 : leftTask.join();
        int rightCount = (rightTask == null) ? 0 : rightTask.join();
        
        return 1 + leftCount + rightCount;
    }
}

Ключевые методы RecursiveTask

public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    
    // Основной метод, который переопределяем
    protected abstract V compute();
    
    // Запускает задачу асинхронно в отдельном потоке
    public final ForkJoinTask<V> fork()
    
    // Ждет завершения и возвращает результат
    public final V join()
    
    // Запускает и сразу ждет (синхронно)
    public final V invoke()
}

ForkJoinPool

public class ForkJoinPoolExample {
    public static void main(String[] args) {
        // Вариант 1: Используем общий пул (ForkJoinPool.commonPool())
        ForkJoinPool commonPool = ForkJoinPool.commonPool();
        MyTask task1 = new MyTask();
        commonPool.invoke(task1);
        
        // Вариант 2: Создаем кастомный пул с определенным количеством потоков
        ForkJoinPool customPool = new ForkJoinPool(
            Runtime.getRuntime().availableProcessors()  // количество потоков
        );
        MyTask task2 = new MyTask();
        customPool.invoke(task2);
        customPool.shutdown();
    }
}

Оптимизация: сбалансированное разделение

public class OptimizedTask extends RecursiveTask<Long> {
    private long[] data;
    private int start;
    private int end;
    
    // Динамически выбираем порог в зависимости от размера
    private static int computeThreshold() {
        int cpus = Runtime.getRuntime().availableProcessors();
        return Math.max(1000, 100_000 / cpus);  // Примерно 100K на процессор
    }
    
    @Override
    protected Long compute() {
        int threshold = computeThreshold();
        
        if (end - start <= threshold) {
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += data[i];
            }
            return sum;
        }
        
        // Разделяем пополам
        int mid = (start + end) / 2;
        OptimizedTask left = new OptimizedTask(data, start, mid);
        OptimizedTask right = new OptimizedTask(data, mid, end);
        
        left.fork();
        long rightResult = right.compute();
        long leftResult = left.join();
        
        return leftResult + rightResult;
    }
}

Когда использовать RecursiveTask

Используй RecursiveTask когда:

  • Задача имеет структуру "разделяй и властвуй"
  • Разделение на подзадачи создает достаточно работы
  • Задача возвращает результат
  • Нужна высокая производительность на многопроцессорных системах

НЕ используй RecursiveTask когда:

  • Задача очень маленькая (overhead Fork/Join больше выигрыша)
  • Нет явного разделения на подзадачи
  • Нужна максимальная простота кода

Заключение

RecursiveTask — это мощный инструмент для параллельной обработки больших объемов данных с использованием стратегии "разделяй и властвуй". Fork/Join framework автоматически управляет потоками и балансирует нагрузку между ними, что делает код и параллелизм эффективными.