[Java] Fork / Join en Java

Inserte la descripción de la imagen aquí

Original: olvidado

¿Qué es Fork / Join?

El marco Fork / Join es un marco de tareas de ejecución paralela proporcionado por Java7. La idea es descomponer tareas grandes en tareas pequeñas, y luego las tareas pequeñas pueden continuar descomponiéndose, y luego los resultados de cada tarea pequeña se calculan por separado y luego se combinan, y finalmente el resultado resumido se usa como Resultado de una gran tarea. La idea es muy similar a la de MapReduce. Para la división de tareas, se requiere que cada subtarea sea independiente entre sí y que pueda ejecutar tareas de forma independiente en paralelo, sin afectarse entre sí.

El diagrama de flujo de operación de Fork / Join es el siguiente:

imagen imagen

Podemos entender este marco a través del significado literal de la palabra Fork / Join. Fork es el significado de fork, que significa que las tareas grandes se descomponen en pequeñas tareas paralelas, y Join es el significado de conexión y combinación, que es resumir los resultados de ejecución de todas las pequeñas tareas paralelas.

Inserte la descripción de la imagen aquí

El algoritmo de robo de trabajo
ForkJoin utiliza un algoritmo de robo de trabajo. Si la cola de tareas de un subproceso de trabajo está vacía y no hay ninguna tarea que ejecutar, obtendrá tareas de otros subprocesos de trabajo y las ejecutará activamente. Para lograr el robo de trabajo, se mantiene una cola de dos extremos en el subproceso de trabajo, el subproceso de tarea que roba obtiene la tarea del final de la cola y el subproceso de tarea robado obtiene la tarea del encabezado de la cola. Este mecanismo hace un uso completo de los subprocesos para la computación en paralelo, lo que reduce la competencia entre subprocesos. Pero cuando solo hay una tarea en la cola, dos subprocesos para recuperarla causarán un desperdicio de recursos.

El diagrama de flujo de operación del robo de trabajo es el siguiente:

Inserte la descripción de la imagen aquí

Fork / Join core class
El marco de Fork / Join se compone principalmente de dos partes: subtareas y programación de tareas El diagrama de jerarquía de clases es el siguiente.

imagen
imagen

HorquillaUnirsePiscina

ForkJoinPool es el programador de tareas en el marco de ForkJoin. Implementa su propio grupo de subprocesos como ThreadPoolExecutor y proporciona tres métodos para programar subtareas:

ejecutar: ejecutar la tarea especificada de forma asincrónica, no se devuelve ningún resultado;
invocar, invocarTodo: ejecutar la tarea especificada de forma asincrónica, esperar a que se complete para devolver el resultado;
enviar: ejecutar la tarea especificada de forma asincrónica y devolver un objeto Future inmediatamente;
Ejecución
real de ForkJoinTask en el marco Fork / Join La clase de tarea tiene las siguientes dos implementaciones. Generalmente, estas dos clases de implementación se pueden heredar.

RecursiveAction: se usa para subtareas sin resultados devueltos;
RecursiveTask: se usa para subtareas con resultados devueltos;

Bifurcación / Combate de marco de unión

Implementemos un pequeño ejemplo de Fork / Join. Desde 1 + 2 +… mil millones, cada tarea solo puede procesar 1000 números y sumarlos. Si más de 1000 se descomponen automáticamente en pequeñas tareas para el procesamiento paralelo; Comparación de consumo de tiempo entre Join y uso.

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

public class ForkJoinTask extends RecursiveTask<Long> {
    
    

	private static final long MAX = 1000000000L;
	private static final long THRESHOLD = 1000L;
	private long start;
	private long end;

	public ForkJoinTask(long start, long end) {
    
    
		this.start = start;
		this.end = end;
	}

	public static void main(String[] args) {
    
    
		test();
		System.out.println("--------------------");
		testForkJoin();
	}

	private static void test() {
    
    
		System.out.println("test");
		long start = System.currentTimeMillis();
		Long sum = 0L;
		for (long i = 0L; i <= MAX; i++) {
    
    
			sum += i;
		}
		System.out.println(sum);
		System.out.println(System.currentTimeMillis() - start + "ms");
	}

	private static void testForkJoin() {
    
    
		System.out.println("testForkJoin");
		long start = System.currentTimeMillis();
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		Long sum = forkJoinPool.invoke(new ForkJoinTask(1, MAX));
		System.out.println(sum);
		System.out.println(System.currentTimeMillis() - start + "ms");
	}

	@Override
	protected Long compute() {
    
    
		long sum = 0;
		if (end - start <= THRESHOLD) {
    
    
			for (long i = start; i <= end; i++) {
    
    
				sum += i;
			}
			return sum;
		} else {
    
    
			long mid = (start + end) / 2;

			ForkJoinTask task1 = new ForkJoinTask(start, mid);
			task1.fork();

			ForkJoinTask task2 = new ForkJoinTask(mid + 1, end);
			task2.fork();

			return task1.join() + task2.join();
		}
	}

}

El resultado del cálculo es necesario aquí, por lo que la tarea hereda la clase RecursiveTask. ForkJoinTask necesita implementar el método de cálculo. En este método, primero debe determinar si la tarea es menor o igual que el umbral 1000 y, de ser así, ejecutar la tarea directamente. De lo contrario, se divide en dos subtareas. Cuando cada subtarea llama al método de bifurcación, ingresará nuevamente al método de cálculo para ver si la subtarea actual debe continuar dividida en tareas secundarias. Si no es necesario continuar dividida, se ejecuta la subtarea actual y se devuelve el resultado. El uso del método de unión bloqueará y esperará a que la subtarea se complete y obtenga su resultado.

Salida del programa:

test
500000000500000000
4992ms
--------------------
testForkJoin
500000000500000000
508ms

Se puede ver en los resultados que el consumo de tiempo del paralelo es significativamente menor que el del serial, lo cual es la ventaja de las tareas paralelas.

Sin embargo, debe tener cuidado al usar Fork / Join, no lo use a ciegas.

Si la tarea está profundamente desarmada, la cantidad de subprocesos en el sistema se acumulará, lo que resultará en una degradación grave del rendimiento del sistema;
si la pila de llamadas de función es profunda, hará que la memoria de la pila se desborde;

Supongo que te gusta

Origin blog.csdn.net/qq_21383435/article/details/108498305
Recomendado
Clasificación