JUC学习系列六(计数器 CountDownLatch)

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrierCountDownLatch 是一个通用同步工具,它有很多用途。

计数 1 初始化CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。

N 初始化CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

场景一:运动员起跑

public class Demo02 {

    public static void main(String[] args) {
        int n=5;
        //开关
        CountDownLatch startSignal = new CountDownLatch(1);
        //计数器
        CountDownLatch doneSignal = new CountDownLatch(n);
        for (int i = 0; i < n; ++i)
            new Thread(new Worker(startSignal, doneSignal,i+"号选手")).start();

//        try {
            System.out.println("准备,3、2、1...");
            //打开开关
            startSignal.countDown();
            System.out.println("开始!");
            //doneSignal.await();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
    }

    static  class Worker implements Runnable{
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;
        private final  String name;
        Worker(CountDownLatch startSignal, CountDownLatch doneSignal,String name) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
            this.name=name;
        }
        @Override
        public void run() {
            try {
                //开关关闭
                startSignal.await();
                doWork(name);
                //计数器倒计时
                doneSignal.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void doWork(String name){
            System.out.println(name+"开始起跑...");
        }
    }

}

 第一个类是一个启动信号,在 driver 为继续执行 worker 做好准备之前,它会阻止所有的 worker 继续执行。

第二个类是一个完成信号,它允许 driver 在完成所有 worker 之前一直等待。

以上是偷懒的写法,以上两个countDownlatch,任意去掉一个都能执行的。

startsignal这个是作为开关的形式,每一个线程任务类里面都调用了await()方法,只有最后执行countDown(),所有的才会一起执行。

doneSignal是作为一种倒计时器的方式,只有所有的线程都到位了,Runnable里面调用了五次countDown(),即doneSignal从5减到了0,所有的线程才会执行。

 《JUC学习系列六(计数器 CountDownLatch)》

 

 开关这种的应该很容易理解,下面我把doneSignal这种计数器的方式拆出来,以便理解

public class Demo01 {

    public static void main(String[] args) {
        int a=5;
        CountDownLatch signal=new CountDownLatch(a);
        for (int i=0;i<a;i++){
            //for循环必须调用五次countDown
             new Thread(new Runner(signal,i+"号选手")).start();
        }
        try {
            //让所有的线程先阻塞着,等countDown清零了,才能放行
            signal.await();
            System.out.println("比赛结束...");
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }

    static class Runner implements Runnable{
        private final CountDownLatch doSignal ;
        private final String runnerName;
        Runner(CountDownLatch c,String name){
            this.doSignal=c;
            this.runnerName=name;
        }
        @Override
        public void run() {
            try {
                prepare(runnerName);
                //倒计时器
                doSignal.countDown();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public void prepare(String name){
            System.out.println("运动员"+name+"抵达终点!");
        }
    }
}

《JUC学习系列六(计数器 CountDownLatch)》

《JUC学习系列六(计数器 CountDownLatch)》

 CountDownLatch使用起来很简单,只要你知道await() countDown()的含义,那么以上两个案例理解起来就很容易了。

CountDownLatch源码剖析:

《JUC学习系列六(计数器 CountDownLatch)》

 countDown的步骤解析:

《JUC学习系列六(计数器 CountDownLatch)》

《JUC学习系列六(计数器 CountDownLatch)》

《JUC学习系列六(计数器 CountDownLatch)》

当countDown将计数器减为0的时候,上面的结果会返回true,if()判断会进入到方法体里面,执行到doReleaseShared()方法。做一些同步器Sync节点Node的释放之类的操作。为共享模式发布操作——信号继承和确保传播。如果不为0,那么直接返回false,到此结束了,所有的方法阻塞着,等待继续countDown()。

《JUC学习系列六(计数器 CountDownLatch)》

await()过程解析:

《JUC学习系列六(计数器 CountDownLatch)》

 

《JUC学习系列六(计数器 CountDownLatch)》

《JUC学习系列六(计数器 CountDownLatch)》

计数器清零时候,不会走if()方法体,await()执行结束,继续向下走。这里讨论一下没有计数器没有清零时,返回-1的情况。

《JUC学习系列六(计数器 CountDownLatch)》

以上就是countDownLatch的countDown和await方法,大致流程。这个需要对AQS的同步器有一定的了解,本次源码解析,没有谈论同步器中Node节点的添加和释放,但是这几张图片也大致可以看到双向链表Node释放的痕迹。感兴趣的可以自己看下源码,和看下本系列第一节的AQS讲解。

 

 

    原文作者:JUC
    原文地址: https://blog.csdn.net/qq_38872310/article/details/81186995
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞