欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java—CountDownLatch使用详解

程序员文章站 2022-05-05 18:24:41
...

CountDownLatch介绍

CountDownLatch概述

  1. CountDownLatch一般用作多线程倒计时计数器,强制它们等待其他一组(CountDownLatch的初始化决定)任务执行完成。
  2. 有一点要说明的是CountDownLatch初始化后计数器值递减到0的时候,不能再复原的,这一点区别于SemaphoreSemaphore是可以通过release操作恢复信号量的。

CountDownLatch使用原理

使用原理

  1. 创建CountDownLatch并设置计数器值。
  2. 启动多线程并且调用CountDownLatch实例的countDown()方法。
  3. 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。

使用模板

public class CountDownLatchModule {

    //线程数
    private static int N = 10;

    // 单位:min
    private static int countDownLatchTimeout = 5;

    public static void main(String[] args) {
        //创建CountDownLatch并设置计数值,该count值可以根据线程数的需要设置
        CountDownLatch countDownLatch = new CountDownLatch(N);

		//创建线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < N; i++) {
            cachedThreadPool.execute(() ->{
                try {
                    System.out.println(Thread.currentThread().getName() + " do something!");
                } catch (Exception e) {
                    System.out.println("Exception: do something exception");
                } finally {
                    //该线程执行完毕-1
                    countDownLatch.countDown();
                }
            });
        }

        System.out.println("main thread do something-1");
        try {
            countDownLatch.await(countDownLatchTimeout, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            System.out.println("Exception: await interrupted exception");
        } finally {
            System.out.println("countDownLatch: " + countDownLatch.toString());
        }
        System.out.println("main thread do something-2");
        //若需要停止线程池可关闭;
//        cachedThreadPool.shutdown();

    }

运行结果:

main thread do something-1
pool-1-thread-1 do something!
pool-1-thread-2 do something!
pool-1-thread-3 do something!
pool-1-thread-5 do something!
pool-1-thread-6 do something!
pool-1-thread-7 do something!
pool-1-thread-8 do something!
pool-1-thread-4 do something!
pool-1-thread-9 do something!
pool-1-thread-10 do something!
countDownLatch: java.util.concurrent.CountDownLatch@76fb509a[Count = 0]
main thread do something-2

CountDownLatch常用方法

Java—CountDownLatch使用详解

  • public void await() throws InterruptedException:调用await()方法的线程会被挂起,等待直到count值为0再继续执行。
  • public boolean await(long timeout, TimeUnit unit) throws InterruptedException:同await(),若等待timeout时长后,count值还是没有变为0,不再等待,继续执行。时间单位如下常用的毫秒、天、小时、微秒、分钟、纳秒、秒。
    Java—CountDownLatch使用详解
  • public void countDown(): count值递减1.
  • public long getCount():获取当前count值。
  • public String toString():重写了toString()方法,多打印了count值,具体参考源码。

CountDownLatch使用场景

一个程序中有N个任务在执行,我们可以创建值为N的CountDownLatch,当每个任务完成后,调用一下countDown()方法进行递减count值,再在主线程中使用await()方法等待任务执行完成,主线程继续执行。

CountDownLatch源码

构造方法源码

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

toString()方法源码

    /**
     * Returns a string identifying this latch, as well as its state.
     * The state, in brackets, includes the String {@code "Count ="}
     * followed by the current count.
     *
     * @return a string identifying this latch, as well as its state
     */
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }

CountDownLatch示例

作为线程启动信号

代码

public class CountDownLatchTest {

    /**
     * a start signal that prevents any worker from proceeding
     * until the driver is ready for them to proceed;
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();
        }
        // don't let run yet
        System.out.println("do something else 1");
        // let all threads proceed
        startSignal.countDown();
        System.out.println("do something else 2");
        // wait for all to finish
        doneSignal.await();
        System.out.println("wait for all to finsh");
    }

    static class Worker implements Runnable{

        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                startSignal.await();
                doWork();
                doneSignal.countDown();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        void doWork() {
            System.out.println("do work!");
        }
    }

}

运行结果

do something else 1
do something else 2
do work!
do work!
do work!
do work!
do work!
do work!
do work!
do work!
do work!
do work!
wait for all to finsh

从运行结果可以看出:

  1. 主线程先打印do something else 1do something else 2。因为startSignal.countDown();完后,count才为0,子线程才能打印。
  2. 因为startSignal.await();是在子线程内,所有子线程都等待startSignal.countDown()执行后才能打印do work!
  3. doneSignal.await();等待所有子线程执行后,每次都doneSignal.countDown(),最后count为0,主线程才执行打印wait for all to finsh

作为线程等待完成信号

代码

public class CountDownLatchTest2 {

    /**
     * a completion signal that allows the driver to wait
     * until all workers have completed.
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch doneSignal = new CountDownLatch(5);
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            // create and start threads
            cachedThreadPool.execute(new Worker(doneSignal, i));
        }
        // don't let run yet
        System.out.println("do something else 1");
        // wait for all to finish
        doneSignal.await();
        System.out.println("===========================count: " + doneSignal.getCount());
        System.out.println("do something else 2");
        cachedThreadPool.shutdown();
    }

    static class Worker implements Runnable{

        private final CountDownLatch doneSignal;
        private final int i;

        Worker(CountDownLatch doneSignal, int i) {
            this.doneSignal = doneSignal;
            this.i = i;
        }

        @Override
        public void run() {
            try {
                doWork();
                doneSignal.countDown();
                System.out.println("i = " + i + ", " + doneSignal.toString());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        void doWork() {
            System.out.println("do work!");
        }
    }

}

运行结果

do something else 1
do work!
i = 0, java.util.concurrent.CountDownLatch@128abd43[Count = 4]
do work!
i = 1, java.util.concurrent.CountDownLatch@128abd43[Count = 3]
do work!
i = 2, java.util.concurrent.CountDownLatch@128abd43[Count = 2]
do work!
i = 3, java.util.concurrent.CountDownLatch@128abd43[Count = 1]
do work!
i = 4, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
===========================count: 0
do something else 2
do work!
i = 5, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 6, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 7, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 8, java.util.concurrent.CountDownLatch@128abd43[Count = 0]
do work!
i = 9, java.util.concurrent.CountDownLatch@128abd43[Count = 0]

从运行结果可以看出,主线程是等待其他线程运行了5次结束后就打印了do something else 2信息,因为CountDownLatch数值为5。