Contenedor y marco concurrente para la programación concurrente de Java

ConcurrentHashMap

ConcurrentHashMap es un HashMap eficiente y seguro para subprocesos.

Principio: dividir los datos en segmentos y almacenarlos en segmentos, y asignar un bloqueo a cada segmento de datos.Cuando un hilo ocupa el bloqueo para acceder a un segmento de datos, los datos de otros segmentos también pueden ser accedidos por otros hilos.

ConcurrentLinkedQueue

Es una cola ilimitada segura para subprocesos basada en nodos de enlace, que utiliza una regla de primero en entrar, primero en salir.

Los nodos están ordenados. Cuando agregamos un elemento, se agregará al final de la cola, cuando obtenemos un elemento, devolverá el elemento al principio de la cola. Utiliza el algoritmo "sin esperar" (algoritmo CAS) para lograrlo.

Cola de bloqueo

Una cola que admite dos operaciones adicionales. Estas dos operaciones adicionales permiten bloquear los métodos de inserción y eliminación.

1) Método de inserción de bloqueo de soporte: esto significa que cuando la cola está llena, la cola bloqueará el hilo que inserta el elemento hasta que la cola no esté llena.

2) Método de eliminación de bloqueo de soporte: esto significa que cuando la cola está vacía, el hilo que obtiene el elemento esperará a que la cola no esté vacía.

Las colas de bloqueo se utilizan a menudo en escenarios de productores y consumidores. Los productores son subprocesos que agregan elementos a la cola y los consumidores son subprocesos que toman elementos de la cola. Una cola de bloqueo es un contenedor que utilizan los productores para almacenar elementos y los consumidores para obtener elementos.

  1. ArrayBlockingQueue

Esta cola ordena los elementos según el principio de primero en entrar, primero en salir (FIFO).

  1. LinkedBlockingQueue

Una cola de bloqueo limitada implementada con una lista vinculada.

  1. PriorityBlockingQueue

Una cola de bloqueo ilimitada que admite prioridad.

  1. DelayQueue

Una cola de bloqueo ilimitada que admite la adquisición retardada de elementos. La cola se implementa mediante PriorityQueue. Los elementos de la cola deben implementar la interfaz Retrasada. Al crear el elemento, puede especificar cuánto tiempo se tarda en obtener el elemento actual de la cola. Solo cuando expira el retraso se pueden extraer elementos de la cola.

  1. SynchronousQueue

Una cola de bloqueo que no almacena elementos. Cada operación de colocación debe esperar una operación de toma; de lo contrario, no puede continuar agregando elementos.

  1. LinkedTransferQueue

Una cola TransferQueue de bloqueo ilimitada compuesta por una estructura de lista enlazada. En comparación con otras colas de bloqueo, LinkedTransferQueue tiene más métodos tryTransfer y transferencia.

  1. LinkedBlockingDeque

Una cola de bloqueo bidireccional compuesta por una estructura de lista enlazada. La llamada cola bidireccional se refiere a

Inserte y elimine elementos de ambos extremos de la cola.

Implementación de cola de bloqueo, ArrayBlockingQueue :

Utilice el modo de notificación para lograrlo.

//不为空的通知

private final Condition notEmpty;

//不为full(队列塞满)的通知

private final Condition notFull;

    public ArrayBlockingQueue(int capacity, boolean fair) {

        notEmpty = lock.newCondition();

        notFull =  lock.newCondition();

}

//阻塞方式插入队列

    public void put(E e) throws InterruptedException {

        checkNotNull(e);

        final ReentrantLock lock = this.lock;

        lock.lockInterruptibly();

        try {

//当队列满员时,将等待入队,等待消费item

            while (count == items.length)

                notFull.await();

            enqueue(e);

        } finally {

            lock.unlock();

        }

}

    private void enqueue(E x) {

        final Object[] items = this.items;

        items[putIndex] = x;

        if (++putIndex == items.length)

            putIndex = 0;

        count++;

//当队列中插入item后,通知消费者继续获取item进行消费

        notEmpty.signal();

    }

//阻塞方式获取元素

    public E take() throws InterruptedException {

        final ReentrantLock lock = this.lock;

        lock.lockInterruptibly();

        try {

//当队列中没有元素时,等待生产者插入item元素

            while (count == 0)

                notEmpty.await();

            return dequeue();

        } finally {

            lock.unlock();

        }

    }

    private E dequeue() {

        final Object[] items = this.items;

        @SuppressWarnings("unchecked")

        E x = (E) items[takeIndex];

        items[takeIndex] = null;

        if (++takeIndex == items.length)

            takeIndex = 0;

        count--;

        if (itrs != null)

            itrs.elementDequeued();

//当队列中item出队时,通知生产者队列有空闲位置,可以继续插入元素

        notFull.signal();

        return x;

    }

A través del código fuente, se puede ver que ArrayBlockQueue es un contenedor concurrente controlado por el bloqueo Lock, y el código es muy simple.

Marco de bifurcación / unión

Un marco para la ejecución paralela de tareas es un marco que divide una tarea grande en varias tareas pequeñas y finalmente resume los resultados de cada tarea pequeña para obtener el resultado de la tarea grande.

El algoritmo de robo de trabajo se refiere a un hilo que roba tareas de otras colas para su ejecución.

Para reducir la competencia entre subprocesos, estas subtareas se colocan en diferentes colas y se crea un subproceso separado para cada cola para ejecutar las tareas en la cola. Los subprocesos y las colas se corresponden uno a uno. Por ejemplo, el hilo A es responsable de procesar tareas en la cola A. Sin embargo, algunos subprocesos terminarán las tareas en sus propias colas primero, mientras que todavía hay tareas esperando ser procesadas en las colas correspondientes a otros subprocesos. En lugar de esperar, el hilo que terminó su trabajo también podría ayudar a otros hilos a trabajar, por lo que fue a la cola de otros hilos para robar una tarea para su ejecución. En este momento, accederán a la misma cola, por lo que para reducir el robo de subprocesos de tareas y ser

La competencia entre los subprocesos de tareas de robo usualmente usa un deque. El subproceso de tarea robado siempre ejecutará la tarea desde el jefe del deque, y el subproceso que roba la tarea siempre ejecutará la tarea desde la cola del deque.

 

Punto de ventajas del algoritmo de robo de trabajo : uso completo de la ruta de la línea en el cálculo del recuento paralelo de las filas , lo que reduce la ruta del cable entre la contienda de la competencia .

Algoritmo de robo de trabajo deficiente: en algunos casos también existe una disputa de competencia , como una fila de equipo de doble punta solo una de cualquier tarea cuando . Y el algoritmo consumirá más fuentes de recursos del sistema , como la creación de columnas de compilación de unidades de línea múltiple de doble extremo y de equipos múltiples .

 Pasos de bifurcación / unión

Paso 1 Divida la tarea.

Paso 2 Ejecute la tarea y combine los resultados.

Ejecución Fork / Join

①ForkJoinTask creación de tareas

RecursiveAction: se utiliza para tareas que no devuelven resultados.

RecursiveTask: se utiliza para tareas que devuelven resultados.

②ForkJoinPool: ForkJoinTask debe ejecutarse a través de ForkJoinPool.

 

ForkJoinTask proporciona el método isCompletedAbnormally () para verificar si la tarea ha generado una excepción o se ha cancelado, y la excepción se puede obtener a través del método getException de ForkJoinTask.

if (task.isCompletedAbnormally ()) {

System.out.println (task.getException ());

}

El método getException devuelve un objeto Throwable, si la tarea se cancela, devuelve una CancellationException. Si la tarea no se completa o no se lanza ninguna excepción, se devuelve un valor nulo.

ForkJoinPool se compone de la matriz ForkJoinTask y la matriz ForkJoinWorkerThread. La matriz ForkJoinTask es responsable de enviar el programa de almacenamiento a las tareas de ForkJoinPool, y la matriz ForkJoinWorkerThread es responsable de realizar estas tareas

 

Hay 4 estados de tarea: completada (NORMAL), cancelada (CANCELADA), señal (SEÑAL) y anormal (EXCEPCIONAL).

Unir dos métodos de asignación de tareas

RecursiveTask:

public class CountTask extends RecursiveTask<Integer> {

    private static final int THRESHOLD = 2; // 阈值
    private int              start;
    private int              end;

    public CountTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;

        // 如果任务足够小就计算任务
        boolean canCompute = (end - start) <= THRESHOLD;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果任务大于阈值,就分裂成两个子任务计算
            int middle = (start + end) / 2;
            CountTask leftTask = new CountTask(start, middle);
            CountTask rightTask = new CountTask(middle + 1, end);
            //执行子任务
            leftTask.fork();
            rightTask.fork();
            //等待子任务执行完,并得到其结果
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
            //合并子任务
            sum = leftResult + rightResult;
        }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        // 生成一个计算任务,负责计算1+2+3+4
        CountTask task = new CountTask(1, 100);
        // 执行一个任务
        Future<Integer> result = forkJoinPool.submit(task);
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    }

}

RecursiveAction:

public class CountAction extends RecursiveAction {

	private static final int THRESHOLD = 2; // 阈值
	private int start;
	private int end;
	private static AtomicInteger atomicInteger=new AtomicInteger();

	public CountAction(int start, int end) {
		this.start = start;
		this.end = end;
	}
	@Override
	protected void compute() {
		int sum = 0;
		// 如果任务足够小就计算任务
		boolean canCompute = (end - start) <= THRESHOLD;
		if (canCompute) {
			for (int i = start; i <= end; i++) {
				sum += i;
			}
			atomicInteger.addAndGet(sum);
		} else {
			// 如果任务大于阈值,就分裂成两个子任务计算
			int middle = (start + end) / 2;
			CountAction leftAction = new CountAction(start, middle);
			CountAction rightAction = new CountAction(middle + 1, end);
			// 执行子任务
			leftAction.fork();
			rightAction.fork();
		}
	}

	public static void main(String[] args) {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		// 生成一个计算任务,负责计算1+2+3+4
		CountAction action = new CountAction(1, 100);
		// 执行一个任务
		forkJoinPool.execute(action);
		forkJoinPool.awaitQuiescence(50, TimeUnit.HOURS);
		System.out.println(action.atomicInteger);
	}

}

 

Supongo que te gusta

Origin blog.csdn.net/weixin_44416039/article/details/86163402
Recomendado
Clasificación