Sobes.tech
Middle+

Cos'è il framework Fork/Join e come viene utilizzato in Java?

sobes.tech AI

Risposta dell'AI

Il framework Fork/Join è un'implementazione specializzata del framework Executor, progettata per parallelizzare efficacemente i compiti che possono essere suddivisi ricorsivamente in sotto-compiti più piccoli e poi uniti (join) i loro risultati. Si basa sul principio di "divide et impera" (divide and conquer).

Componenti chiave:

  • ForkJoinPool: Un pool di thread che gestisce l'esecuzione dei compiti. Utilizza un meccanismo di "work-stealing", in cui i thread inattivi nel pool possono "rubare" compiti da altri thread occupati.
  • ForkJoinTask: Classe astratta che rappresenta un compito che può essere eseguito in questo framework. Ha due sottotipi:
    • RecursiveAction: Compito che non restituisce risultato.
    • RecursiveTask<V>: Compito che restituisce un risultato di tipo V.

Utilizzo:

  1. Creare una classe che erediti da RecursiveAction o RecursiveTask.
  2. Sovrascrivere il metodo compute(). In questo metodo si descrive la logica:
    • Se il compito è abbastanza piccolo (inferiore a una soglia), viene eseguito direttamente.
    • In caso contrario, il compito viene suddiviso in sotto-compiti, che vengono eseguiti in modo asincrono tramite fork().
    • Si attende il completamento dei sotto-compiti con join() e si uniscono i loro risultati.
  3. Creare un'istanza di ForkJoinPool.
  4. Sottomettere il compito al pool usando invoke() o submit().

Esempio:

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; // Soglia

    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;
        }
    }
}

// Utilizzo nel codice principale
// long[] data = new long[10000]; // Dati iniziali
// // Riempire data
// ForkJoinPool pool = new ForkJoinPool();
// long sum = pool.invoke(new SumArrayTask(data, 0, data.length));
// pool.shutdown(); // Chiudere il pool dopo l'uso

Vantaggi:

  • Gestione automatica del pool di thread.
  • Distribuzione efficiente del carico grazie al "work-stealing".
  • Miglioramento delle prestazioni per compiti di tipo "divide et impera".

Svantaggi:

  • Non adatto a tutti i tipi di compiti paralleli.
  • Potrebbe essere più complesso da implementare rispetto ad altri approcci per compiti semplici.