El uso de CountDownLatch y análisis de código fuente, implementación de escritura a mano

El uso y análisis del código fuente de CountDownLatch

CountDownLatch se conoce comúnmente como un bloqueo, que permite que uno o más subprocesos esperen a que otros subprocesos completen la operación especificada antes de ejecutarse.

El constructor de CountDownLatch acepta un parámetro de tipo int como contador. Si desea esperar a que se completen los N puntos, pase N aquí.

Cuando llamamos al método countDown de CountDownLatch, N se reducirá en 1 y el método await de CountDownLatch bloqueará el hilo actual hasta que N se convierta en cero.

Dado que el método countDown se puede usar en cualquier lugar, los N puntos mencionados aquí pueden ser N subprocesos o N pasos de ejecución en un subproceso (un subproceso puede contar hacia atrás varias veces). Cuando se usa en varios hilos, simplemente pase la referencia CountDownLatch al hilo.

Métodos en CountDownLatch

Nombre del método Descripción
CountDownLatch (recuento int) Construya un CountDownLatch con un recuento dado
vacío espera () El hilo actual espera hasta que el número de bloqueos llegue a cero, a menos que se interrumpa el hilo
espera booleana (tiempo de espera largo, unidad TimeUnit) El hilo actual espera hasta que el número de bloqueos llegue a cero, a menos que el hilo se interrumpa o se agote el tiempo
vacío countDown () Disminuya el recuento bloqueado, si el recuento llega a cero, despierte todos los hilos en espera bloqueados
long getCount () Devolver el recuento actual

Uso de CountDownLatch

Si existe tal requisito: necesitamos analizar los datos de varias hojas en un Excel. En este momento, podemos considerar el uso de subprocesos múltiples. Cada subproceso analiza los datos en una hoja. Después de analizar todas las hojas, el programa debe indicar que el análisis está completo. .

package com.morris.concurrent.tool.countdownlatch.api;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 演示CountDownLatch闭锁的使用
 */
@Slf4j
public class CountDownLatchDemo {
    
    

    private static CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {
    
    

        new Thread(()->parse("sheet1"), "t1").start();
        new Thread(()->parse("sheet2"), "t2").start();
        new Thread(()->parse("sheet3"), "t3").start();

        countDownLatch.await();
        log.info("parse commplete");
    }

    private static void parse(String sheet) {
    
    
        log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
        try {
    
    
            TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
        countDownLatch.countDown();
        log.info("还有{}个sheet未解析完", countDownLatch.getCount());
    }
}

Los resultados son los siguientes:

2020-09-24 13:58:55,551  INFO [t2] (CountDownLatchDemo.java:28) - t2 parse sheet2 begin...
2020-09-24 13:58:55,565  INFO [t1] (CountDownLatchDemo.java:28) - t1 parse sheet1 begin...
2020-09-24 13:58:55,597  INFO [t3] (CountDownLatchDemo.java:28) - t3 parse sheet3 begin...
2020-09-24 13:59:01,565  INFO [t1] (CountDownLatchDemo.java:34) - t1 parse sheet1 end.
2020-09-24 13:59:01,566  INFO [t1] (CountDownLatchDemo.java:36) - 还有2个sheet未解析完
2020-09-24 13:59:02,599  INFO [t3] (CountDownLatchDemo.java:34) - t3 parse sheet3 end.
2020-09-24 13:59:02,599  INFO [t3] (CountDownLatchDemo.java:36) - 还有1个sheet未解析完
2020-09-24 13:59:24,556  INFO [t2] (CountDownLatchDemo.java:34) - t2 parse sheet2 end.
2020-09-24 13:59:24,556  INFO [t2] (CountDownLatchDemo.java:36) - 还有0个sheet未解析完
2020-09-24 13:59:24,556  INFO [main] (CountDownLatchDemo.java:24) - parse commplete

Por supuesto, también puede usar join () para lograr:

package com.morris.concurrent.tool.countdownlatch.api;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 使用join完成对excel的解析,与countDownLatch对比
 */
@Slf4j
public class JoinDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    

        Thread t1 = new Thread(() -> parse("sheet1"), "t1");
        t1.start();
        Thread t2 = new Thread(() -> parse("sheet2"), "t2");
        t2.start();
        Thread t3 = new Thread(() -> parse("sheet3"), "t3");
        t3.start();

        t1.join();
        t2.join();
        t3.join();
        log.info("parse commplete");
    }

    public static void parse(String sheet) {
    
    
        log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
        try {
    
    
            TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
    }
}

Los resultados son los siguientes:

2020-09-24 14:04:57,732  INFO [t2] (JoinDemo.java:30) - t2 parse sheet2 begin...
2020-09-24 14:04:57,755  INFO [t3] (JoinDemo.java:30) - t3 parse sheet3 begin...
2020-09-24 14:04:57,736  INFO [t1] (JoinDemo.java:30) - t1 parse sheet1 begin...
2020-09-24 14:05:07,757  INFO [t3] (JoinDemo.java:36) - t3 parse sheet3 end.
2020-09-24 14:05:07,758  INFO [t1] (JoinDemo.java:36) - t1 parse sheet1 end.
2020-09-24 14:05:07,757  INFO [t2] (JoinDemo.java:36) - t2 parse sheet2 end.
2020-09-24 14:05:07,759  INFO [main] (JoinDemo.java:26) - parse commplete

Comparación de CountDownLatch y unirse:

  • CountDownLatch es más flexible que join. Join debe obtener el objeto del hilo antes de que se pueda usar. Por lo general, se usa el grupo de hilos. Los hilos del grupo de hilos no están expuestos al exterior y no se puede usar el join.
  • Join debe esperar a que el hilo termine antes de regresar, y CountDownLatch.await () puede regresar después de que el hilo se esté ejecutando a la mitad.

Análisis del código fuente de CountDownLatch

La capa inferior de CountDownLatch se basa en AQS.

estructura de datos

java.util.concurrent.CountDownLatch.Sync

private static final class Sync extends AbstractQueuedSynchronizer {
    
    

    Sync(int count) {
    
    
        setState(count); // 构造时初始化count的大小
    }

    int getCount() {
    
    
        return getState();
    }

    // await()调用此方法,不为0就会进入同步队列中等待,为0就会直接返回,往下执行
    protected int tryAcquireShared(int acquires) {
    
    
        return (getState() == 0) ? 1 : -1;
    }

    // countDown()调用此方法,每调用一次state就会-1,当state=0时,会去唤醒同步队列中等待的线程
    protected boolean tryReleaseShared(int releases) {
    
    
        // Decrement count; signal when transition to zero
        for (;;) {
    
    
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

cuenta regresiva()

java.util.concurrent.CountDownLatch # countDown

public void countDown() {
    
    
    sync.releaseShared(1);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer # releaseShared

public final boolean releaseShared(int arg) {
    
    
    if (tryReleaseShared(arg)) {
    
     // state-1
        doReleaseShared(); // 唤醒同步队列中等待的线程
        return true;
    }
    return false;
}

esperar()

java.util.concurrent.CountDownLatch # await ()

public void await() throws InterruptedException {
    
    
    sync.acquireSharedInterruptibly(1);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer # adquiridoSharedInterruptiblemente

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    
    
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0) // 判断state是否为0,是就会直接返回
        doAcquireSharedInterruptibly(arg); // 进入同步队列中等待,park
}

CountDownLatch personalizado

package com.morris.concurrent.tool.countdownlatch.my;

import java.util.concurrent.TimeUnit;

/**
 * 使用wait-notify实现CountDownLatch
 */
public class WaitNotifyCountDownLatch {
    
    

    private volatile int count;

    public WaitNotifyCountDownLatch(int count) {
    
    
        this.count = count;
    }

    public synchronized void countDown() {
    
    
        if (0 == --count) {
    
    
            this.notifyAll();
        }
    }

    public synchronized void await() throws InterruptedException {
    
    
        while (count > 0) {
    
    
            this.wait();
        }
    }
    
    public synchronized void await(long timeout, TimeUnit unit) throws InterruptedException {
    
    
        while (count > 0) {
    
    
            this.wait(unit.toMillis(timeout));
        }
    }

    public int getCount() {
    
    
        return count;
    }
}

Supongo que te gusta

Origin blog.csdn.net/u022812849/article/details/108795637
Recomendado
Clasificación