Sobes.tech
Middle+

Czym jest framework Fork/Join i jak jest używany w Java?

sobes.tech AI

Odpowiedź od AI

Framework Fork/Join to specjalistyczna implementacja frameworku Executor, zaprojektowana do efektywnego równoległego wykonywania zadań, które mogą być rekurencyjnie dzielone na mniejsze podzadania, a następnie łączone (join) ich wyniki. Opiera się na zasadzie "dziel i zwyciężaj" (divide and conquer).

Kluczowe komponenty:

  • ForkJoinPool: Pula wątków, która zarządza wykonywaniem zadań. Używa mechanizmu "work-stealing", w którym nieaktywne wątki mogą "kraść" zadania od innych zajętych wątków.
  • ForkJoinTask: Klasa abstrakcyjna reprezentująca zadanie, które może być wykonane w tym frameworku. Ma dwa podtypy:
    • RecursiveAction: Zadanie, które nie zwraca wyniku.
    • RecursiveTask<V>: Zadanie, które zwraca wynik typu V.

Użycie:

  1. Tworzy się klasę dziedziczącą po RecursiveAction lub RecursiveTask.
  2. Nadpisuje się metodę compute(). W tej metodzie opisuje się logikę:
    • Jeśli zadanie jest wystarczająco małe (mniejsze od pewnego progu), jest wykonywane bezpośrednio.
    • W przeciwnym razie, zadanie jest dzielone na podzadania, które są uruchamiane asynchronicznie metodą fork().
    • Oczekuje się na zakończenie podzadań metodą join() i łączy ich wyniki.
  3. Tworzy się instancję ForkJoinPool.
  4. Zadanie jest składane do puli za pomocą invoke() lub submit().

Przykład:

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

class SumArrayTask extends RecursiveTask<Long> {
    private final long[] array;
    private final int start;
    private final int end;
    private static final int THRESHOLD = 1000; // Próg

    public SumArrayTask(long[] 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 - start) / 2;
            SumArrayTask leftTask = new SumArrayTask(array, start, mid);
            SumArrayTask rightTask = new SumArrayTask(array, mid, end);

            leftTask.fork();
            Long rightResult = rightTask.compute();
            Long leftResult = leftTask.join();

            return leftResult + rightResult;
        }
    }
}

// Użycie w głównym kodzie
// long[] data = new long[10000]; // Dane wejściowe
// // Wypełnić dane
// ForkJoinPool pool = new ForkJoinPool();
// long sum = pool.invoke(new SumArrayTask(data, 0, data.length));
// pool.shutdown(); // Zamknąć pulę po użyciu

Zalety:

  • Automatyczne zarządzanie pulą wątków.
  • Efektywne rozkładanie obciążenia dzięki "work-stealing".
  • Zwiększona wydajność dla zadań typu "divide and conquer".

Wady:

  • Nie nadaje się do wszystkich typów zadań równoległych.
  • Może być trudniejsze do implementacji w porównaniu z innymi podejściami dla prostych zadań.