Skip to content

1 Introducción al ForkJoin framework

Intro

Como hemos visto, Java 5 introdujo el concepto de ejecutor, a través de las interfaces Executor y ExecutorService, y de las clases que las implementan, como ThreadPoolExecutor. El uso de ejecutores permite separar por un lado la creación de la tarea y por otro lado su ejecución. Tan sólo es necesario crear las tareas y enviárselas al ejecutor, que es quien crea, gestiona y finaliza los hilos necesarios para la ejecución de las tareas.

Java 7 va un paso más allá proporcionándonos una implementación adicional de la interfaz ExecutorService, conocida como fork/join framework, que proporciona un entorno de trabajo de alto rendimiento para el paralelismo de datos en Java, es decir para que se realicen tareas sobre una colección de datos de forma concurrente, aprovechando la disponibilidad de varios núcleos de procesamiento.

Este framework está diseñado para ser usado con tareas que pueden descomponerse en subtareas. Está orientado a la resolución concurrente de un problema mediante la técnica divide y vencerás, consistente en descomponer un problema en subproblemas más simples, cuya operación se conoce como fork, obtener la solución de dichos subproblemas, y componer la solución total en base a la solución de los subproblemas, a lo que se conoce como join. El algoritmo sería algo así:

if problem is small enough
    solve problem directly (sequential algorithm)
else
    split problem into independent parts
    fork new sub-tasks to solve each part
    sub-tasks are solved in parallel, even in different threads
    join all sub-tasks
    compose result from sub-results

Así, el framework se basa en dos operaciones principales:

  • Descomposición (fork): Divide una tarea en subtareas más pequeñas y las ejecuta.
  • Composición (join): La tarea espera la finalización de las subtareas en las que se ha descompuesto. Cuando una tarea se encuentra en esta situación, el hilo en el que se está ejecutando es asignado a otra tarea del ejecutor que no haya sido ejecutada aún. A este mecanismo se le conoce como algoritmo de robo de trabajo (work-stealing algorithm).

Fork y Join

Figura 1 - Fork y Join

Las tareas que pueden ser utilizadas con este framework tienen una serie de limitaciones:

  • Sólo pueden utilizar como mecanismos de sincronización los métodos fork() y join(). Si utilizan otros mecanismos de sincronización, no podrá ser usado el algoritmo de robo de trabajo, es decir, que los hilos del ejecutor no podrán ser reusados por otras tareas mientras la tarea esté en una operación de sincronización (por ejemplo si se pone a dormir).
  • No deben realizar operaciones de Entrada/Salida, como leer o escribir datos en un archivo.
  • No deben lanzar excepciones chequeadas (checked exceptions). Deben incluir el código necesario para procesar dichas excepciones.

El núcleo del entorno está formado por las siguientes dos clases: ForkJoinPool y ForkJoinTask.