Sobes.tech
Middle+

¿Qué es el marco Fork/Join y cómo se usa en Java?

sobes.tech AI

Respuesta de la IA

El marco Fork/Join es una implementación especializada del marco de trabajo Executor, diseñada para paralelizar tareas de manera eficiente, que pueden dividirse recursivamente en sub-tareas más pequeñas y luego unirse (join) sus resultados. Se basa en el principio de "divide y vencerás" (divide and conquer).

Componentes clave:

  • ForkJoinPool: Un grupo de hilos que gestiona la ejecución de tareas. Utiliza un mecanismo de "work-stealing", donde los hilos ociosos en el grupo pueden "robar" tareas de otros hilos que están ocupados.
  • ForkJoinTask: Clase abstracta que representa una tarea que puede ser ejecutada en este marco. Tiene dos subtipos:
    • RecursiveAction: Tarea que no devuelve resultado.
    • RecursiveTask<V>: Tarea que devuelve un resultado de tipo V.

Uso:

  1. Crear una clase que herede de RecursiveAction o RecursiveTask.
  2. Sobrescribir el método compute(). En este método se describe la lógica:
    • Si la tarea es lo suficientemente pequeña (menor que un umbral), se ejecuta directamente.
    • En caso contrario, la tarea se divide en sub-tareas, que se ejecutan de forma asíncrona mediante fork().
    • Se espera a que las sub-tareas terminen con join() y se unen sus resultados.
  3. Crear una instancia de ForkJoinPool.
  4. La tarea se envía al pool usando invoke() o submit().

Ejemplo:

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

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

// Uso en código principal
// long[] data = new long[10000]; // Datos iniciales
// // Llenar data
// ForkJoinPool pool = new ForkJoinPool();
// long sum = pool.invoke(new SumArrayTask(data, 0, data.length));
// pool.shutdown(); // Cerrar el pool después del uso

Ventajas:

  • Gestión automática del pool de hilos.
  • Distribución eficiente de la carga gracias a work-stealing.
  • Mejor rendimiento para tareas del tipo "divide y vencerás".

Desventajas:

  • No apto para todos los tipos de tareas paralelas.
  • Puede ser más difícil de implementar en comparación con otros enfoques para tareas simples.