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

Является ли ForkJoinPoll имплементацией ExecutorService?

2.0 Middle🔥 71 комментариев
#Основы Java

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

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

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

ForkJoinPool и ExecutorService

Да, ForkJoinPool является имплементацией ExecutorService. Это специализированная реализация, оптимизированная для параллельных задач типа divide-and-conquer. Давайте разберёмся в иерархии и особенностях.

Иерархия классов

Executor (interface)
    ↓
ExecutorService (interface)
    ↓
AbstractExecutorService (abstract class)
    ↓
ForkJoinPool (final class)

ForkJoinPool реализует все методы ExecutorService:

public class ForkJoinPool extends AbstractExecutorService {
    // Реализует все методы ExecutorService
}

Различие между обычным ExecutorService и ForkJoinPool

Обычный ExecutorService (например, ThreadPoolExecutor)

// Для работ, которые независимы друг от друга
ExecutorService executor = Executors.newFixedThreadPool(4);

// Работа 1
executor.submit(() -> {
    System.out.println("Task 1");
});

// Работа 2
executor.submit(() -> {
    System.out.println("Task 2");
});

// Работы выполняются параллельно
executor.shutdown();

ForkJoinPool (для recursive задач)

// Для задач, которые разбиваются на подзадачи
public class MergeSort extends RecursiveTask<int[]> {
    private int[] array;
    private int start;
    private int end;
    
    @Override
    protected int[] compute() {
        // Базовый случай
        if (end - start <= 10) {
            return simpleSort();
        }
        
        // Разделить (fork)
        int mid = (start + end) / 2;
        MergeSort left = new MergeSort(array, start, mid);
        MergeSort right = new MergeSort(array, mid, end);
        
        left.fork();
        right.fork();
        
        // Соединить (join)
        int[] leftResult = left.join();
        int[] rightResult = right.join();
        
        return merge(leftResult, rightResult);
    }
}

// Использование ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
int[] sorted = pool.invoke(new MergeSort(array, 0, array.length));
pool.shutdown();

Ключевые особенности ForkJoinPool

1. Work Stealing алгоритм

Когда один поток заканчивает свою очередь, он может забрать задачу у другого потока:

Поток 1: Task1 Task2 Task3 [idle]
Поток 2: Task4 Task5 Task6 Task7 Task8

Поток 1 может "украсть" Task7 или Task8 у Потока 2

2. Работа с RecursiveTask

// Для операций, которые возвращают результат
public class ComputeTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 1000;
    private long[] numbers;
    private int start;
    private int end;
    
    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            // Базовый случай: вычислить напрямую
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += numbers[i];
            }
            return sum;
        } else {
            // Рекурсивный случай: разделить
            int mid = (start + end) / 2;
            ComputeTask left = new ComputeTask(numbers, start, mid);
            ComputeTask right = new ComputeTask(numbers, mid, end);
            
            left.fork();
            return right.compute() + left.join();
        }
    }
}

3. Работа с RecursiveAction

// Для операций без возвращаемого значения
public class PrintTask extends RecursiveAction {
    private static final int THRESHOLD = 100;
    private int[] numbers;
    private int start;
    private int end;
    
    @Override
    protected void compute() {
        if (end - start <= THRESHOLD) {
            for (int i = start; i < end; i++) {
                System.out.println(numbers[i]);
            }
        } else {
            int mid = (start + end) / 2;
            PrintTask left = new PrintTask(numbers, start, mid);
            PrintTask right = new PrintTask(numbers, mid, end);
            
            left.fork();
            right.fork();
            
            left.join();
            right.join();
        }
    }
}

Сравнение ExecutorService и ForkJoinPool

ХарактеристикаExecutorServiceForkJoinPool
Тип задачНезависимые задачиRecursive разделяемые задачи
ОчередьFIFO per threadDeque per thread (work-stealing)
ОптимизацияДля I/O операцийДля CPU-intensive операций
Синхронизацияsubmit() / invokeAll()fork() / join()
ПроизводительностьХорошая для немногоОтличная для deeply recursive

Практические примеры

Пример 1: Быстрая сортировка с ForkJoinPool

public class QuickSort extends RecursiveTask<Void> {
    private int[] array;
    private int low;
    private int high;
    private static final int THRESHOLD = 100;
    
    @Override
    protected Void compute() {
        if (high - low <= THRESHOLD) {
            quickSortSequential(array, low, high);
        } else {
            int partitionIndex = partition(array, low, high);
            
            QuickSort left = new QuickSort(array, low, partitionIndex - 1);
            QuickSort right = new QuickSort(array, partitionIndex, high);
            
            left.fork();
            right.fork();
            
            left.join();
            right.join();
        }
        return null;
    }
    
    private int partition(int[] array, int low, int high) {
        int pivot = array[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (array[j] < pivot) {
                i++;
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
        int temp = array[i + 1];
        array[i + 1] = array[high];
        array[high] = temp;
        return i + 1;
    }
}

// Использование
ForkJoinPool pool = ForkJoinPool.commonPool();
pool.invoke(new QuickSort(array, 0, array.length - 1));

Пример 2: Fibonacci с ForkJoinPool

public class Fibonacci extends RecursiveTask<Long> {
    private long n;
    private static final int THRESHOLD = 20;
    
    @Override
    protected Long compute() {
        if (n <= 1) {
            return n;
        }
        if (n <= THRESHOLD) {
            return fibSequential(n);
        }
        
        Fibonacci f1 = new Fibonacci(n - 1);
        Fibonacci f2 = new Fibonacci(n - 2);
        
        f1.fork();
        return f2.compute() + f1.join();
    }
    
    private long fibSequential(long n) {
        if (n <= 1) return n;
        return fibSequential(n - 1) + fibSequential(n - 2);
    }
}

// Использование
long result = ForkJoinPool.commonPool().invoke(new Fibonacci(40));
System.out.println("Fibonacci(40) = " + result);

Методы ExecutorService, доступные в ForkJoinPool

ForkJoinPool pool = new ForkJoinPool();

// execute() — отправить задачу
pool.execute(new MyTask());

// submit() — отправить задачу и получить Future
Future<Integer> future = pool.submit(new MyTask());
Integer result = future.get();  // Ждём результата

// invokeAll() — отправить несколько задач и ждать все
List<RecursiveTask<Integer>> tasks = Arrays.asList(
    new Task1(),
    new Task2(),
    new Task3()
);
pool.invokeAll(tasks);

// shutdown() и shutdownNow()
pool.shutdown();
pool.awaitTermination(1, TimeUnit.MINUTES);

Best Practices

  1. Используйте ForkJoinPool для recursive задач, а не для простых параллельных
  2. Установите подходящий THRESHOLD, чтобы избежать чрезмерного разделения
  3. Используйте commonPool() для большинства случаев:
ForkJoinTask<Integer> task = ForkJoinTask.adapt(() -> {
    return heavyComputation();
});
Integer result = ForkJoinPool.commonPool().invoke(task);
  1. Не блокируйте потоки в ForkJoinPool задачах
  2. Используйте join() вместо get() для большей эффективности

Заключение

ForkJoinPool — это имплементация ExecutorService, специализирующаяся на параллельных операциях типа divide-and-conquer. Она использует уникальный work-stealing алгоритм и оптимизирована для рекурсивных задач. В то время как обычный ExecutorService подходит для независимых задач, ForkJoinPool блистает при обработке больших рекурсивных структур данных и CPU-intensive операций.