CountDownLatch与CyclicBarrier_如来神掌十八式的博客-程序员信息网_countdownlatch与cyclicbarrier

技术标签: 多线程  countDownLatch  

倒计时器CountDownLatch

在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join方法,让主线程等待被join的线程执行完之后,主线程才能继续往下执行。当然,使用线程间消息通信机制也可以完成。其实,Java并发工具类中为我们提供了类似“倒计时”这样的工具类,可以十分方便的完成所说的这种业务场景。

为了能够理解CountDownLatch,举一个很通俗的例子,运动员进行跑步比赛时,假设有6个运动员参与比赛,裁判员在终点会为这6个运动员分别计时,可以想象每当一个运动员到达终点的时候,对于裁判员来说就少了一个计时任务。直到所有运动员都到达终点了,裁判员的任务也才完成。这6个运动员可以类比成6个线程,当线程调用CountDownLatch.countDown方法时就会对计数器的值减一,直到计数器的值为0的时候,裁判员(调用await方法的线程)才能继续往下执行。

下面来看些CountDownLatch的一些重要方法。

先从CountDownLatch的构造方法看起:

public CountDownLatch(int count)

构造方法会传入一个整型数N,之后调用CountDownLatch的countDown方法会对N减一,直到N减到0的时候,当前调用await方法的线程继续执行。

CountDownLatch的方法不是很多,将它们一个个列举出来:

  1. await() throws InterruptedException:调用该方法的线程等到构造方法传入的N减到0的时候,才能继续往下执行;
  2. await(long timeout, TimeUnit unit):与上面的await方法功能一致,只不过这里有了时间限制,调用该方法的线程等到指定的timeout时间后,不管N是否减至为0,都会继续往下执行;
  3. countDown():使CountDownLatch初始值N减1;
  4. long getCount():获取当前CountDownLatch维护的值;

下面用一个具体的例子来说明CountDownLatch的具体用法:

public class CountDownLatchDemo {
    

    private static CountDownLatch startSignal = new CountDownLatch(1);
    //用来表示裁判员需要维护的是6个运动员
    private static CountDownLatch endSignal = new CountDownLatch(6);

    public static void main(String[] args) throws InterruptedException {
    
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        System.out.println("各位运动员准备啦!!!");
        for (int i = 0; i < 6; i++) {
    
            executorService.execute(() -> {
    
                try {
    
                    System.out.println(Thread.currentThread().getName() + " 运动员等待裁判员响哨!!!");
                    // 确保所有运动员准备完毕
                    Thread.sleep(1000);
                    startSignal.await();

                    System.out.println(Thread.currentThread().getName() + " 正在全力冲刺");
                    endSignal.countDown();
                    System.out.println(Thread.currentThread().getName() + " 到达终点");
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
            });
        }

        //将executorService转换为ThreadPoolExecutor,ThreadPoolExecutor有方法 getActiveCount()可以得到当前活动线程数
        int threadCount = ((ThreadPoolExecutor)executorService).getActiveCount();
        if (threadCount == 6) {
    
            System.out.println("裁判员响哨...");
            startSignal.countDown();

            endSignal.await();
            System.out.println("所有运动员到达终点,比赛结束!");
        }


        executorService.shutdown();
    }

}

输出结果

各位运动员准备啦!!!
pool-1-thread-1 运动员等待裁判员响哨!!!
pool-1-thread-3 运动员等待裁判员响哨!!!
pool-1-thread-2 运动员等待裁判员响哨!!!
pool-1-thread-5 运动员等待裁判员响哨!!!
pool-1-thread-4 运动员等待裁判员响哨!!!
pool-1-thread-6 运动员等待裁判员响哨!!!
裁判员响哨...
pool-1-thread-5 正在全力冲刺
pool-1-thread-5 到达终点
pool-1-thread-6 正在全力冲刺
pool-1-thread-2 正在全力冲刺
pool-1-thread-2 到达终点
pool-1-thread-1 正在全力冲刺
pool-1-thread-1 到达终点
pool-1-thread-3 正在全力冲刺
pool-1-thread-3 到达终点
pool-1-thread-4 正在全力冲刺
pool-1-thread-6 到达终点
pool-1-thread-4 到达终点
所有运动员到达终点,比赛结束!

该示例代码中设置了两个CountDownLatch,第一个endSignal用于控制让main线程(裁判员)必须等到其他线程(运动员)让CountDownLatch维护的数值N减到0为止。另一个startSignal用于让main线程对其他线程进行“发号施令”,startSignal引用的CountDownLatch初始值为1,而其他线程执行的run方法中都会先通过 startSignal.await()让这些线程都被阻塞,直到main线程通过调用startSignal.countDown();,将值N减1,CountDownLatch维护的数值N为0后,其他线程才能往下执行,并且,每个线程执行的run方法中都会通过endSignal.countDown();endSignal维护的数值进行减一,由于往线程池提交了6个任务,会被减6次,所以endSignal维护的值最终会变为0,因此main线程在latch.await();阻塞结束,才能继续往下执行。

另外,需要注意的是,当调用CountDownLatch的countDown方法时,当前线程是不会被阻塞,会继续往下执行,比如在该例中会继续输出pool-1-thread-4 到达终点

循环栅栏CyclicBarrier

CyclicBarrier也是一种多线程并发控制的实用工具,和CountDownLatch一样具有等待计数的功能,但是相比于CountDownLatch功能更加强大。

为了理解CyclicBarrier,这里举一个通俗的例子。开运动会时,会有跑步这一项运动,我们来模拟下运动员入场时的情况,假设有6条跑道,在比赛开始时,就需要6个运动员在比赛开始的时候都站在起点了,裁判员吹哨后才能开始跑步。跑道起点就相当于“barrier”,是临界点,而这6个运动员就类比成线程的话,就是这6个线程都必须到达指定点了,意味着凑齐了一波,然后才能继续执行,否则每个线程都得阻塞等待,直至凑齐一波即可。cyclic是循环的意思,也就是说CyclicBarrier当多个线程凑齐了一波之后,仍然有效,可以继续凑齐下一波。CyclicBarrier的执行示意图如下:

在这里插入图片描述

当多个线程都达到了指定点后,才能继续往下继续执行。这就有点像报数的感觉,假设6个线程就相当于6个运动员,到赛道起点时会报数进行统计,如果刚好是6的话,这一波就凑齐了,才能往下执行。CyclicBarrier在使用一次后,下面依然有效,可以继续当做计数器使用,这是与CountDownLatch的区别之一。这里的6个线程,也就是计数器的初始值6,是通过CyclicBarrier的构造方法传入的。

下面来看下CyclicBarrier的主要方法:

//等到所有的线程都到达指定的临界点
await() throws InterruptedException, BrokenBarrierException 

//与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止
await(long timeout, TimeUnit unit) throws InterruptedException, 
BrokenBarrierException, TimeoutException 

//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()

//用于查询阻塞等待的线程是否被中断
boolean isBroken()

//将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。
void reset()


另外需要注意的是,CyclicBarrier提供了这样的构造方法:

public CyclicBarrier(int parties, Runnable barrierAction)

可以用来,当指定的线程都到达了指定的临界点的时,接下来执行的操作可以由barrierAction传入即可。

一个例子

下面用一个简单的例子,来看下CyclicBarrier的用法,我们来模拟下上面的运动员的例子。

public class CyclicBarrierDemo {
    

    //指定必须有6个运动员到达才行
    private static CyclicBarrier barrier = new CyclicBarrier(6, () ->
            System.out.println("所有运动员入场,裁判员一声令下!!!!!")
    );

    public static void main(String[] args) {
    
        System.out.println("运动员准备进场,全场欢呼............");

        ExecutorService service = Executors.newFixedThreadPool(6);
        for (int i = 0; i < 6; i++) {
    
            service.execute(() -> {
    
                try {
    
                    System.out.println(Thread.currentThread().getName() + " 运动员,进场");
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + "  运动员出发");
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
                    e.printStackTrace();
                }
            });
        }
    }

}

输出结果

运动员准备进场,全场欢呼............
pool-1-thread-1 运动员,进场
pool-1-thread-4 运动员,进场
pool-1-thread-3 运动员,进场
pool-1-thread-2 运动员,进场
pool-1-thread-6 运动员,进场
pool-1-thread-5 运动员,进场
所有运动员入场,裁判员一声令下!!!!!
pool-1-thread-5  运动员出发
pool-1-thread-1  运动员出发
pool-1-thread-6  运动员出发
pool-1-thread-3  运动员出发
pool-1-thread-2  运动员出发
pool-1-thread-4  运动员出发

从输出结果可以看出,当6个运动员(线程)都到达了指定的临界点(barrier)时候,才能继续往下执行,否则,则会阻塞等待在调用await()

CountDownLatch与CyclicBarrier的比较

CountDownLatch与CyclicBarrier都是用于控制并发的工具类,都可以理解成维护的就是一个计数器,但是这两者还是各有不同侧重点的:

  1. CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成,再携手共进。
  2. 调用CountDownLatch的countDown方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrier的await方法,会阻塞当前线程,直到CyclicBarrier指定的线程全部都到达了指定点的时候,才能继续往下执行;
  3. CountDownLatch方法比较少,操作比较简单,而CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能;
  4. CountDownLatch是不能复用的,而CyclicLatch是可以复用的。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/t194978/article/details/123610589

智能推荐

VScode配置c++环境(MinGW安装教程)_朽木白露的博客-程序员信息网_mingw vscode

VScode配置C++环境需要安装MinGW编译器:安装MinGW下载可以下载这个:https://blog.csdn.net/shiaiao/article/details/90759813?utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase&amp;depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1

java设计模式_呆萌坦然的博客-程序员信息网

软件工程中,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。拿实际工作经历来说,当一个项目开发完后,如果客户提出新增功能,怎么办?如果项目开发完后,原来的工程师离职,你接手维护项目怎么办?以上两个问题,都要求我们的代码有可扩展性、维护性(可读性、规范性)特性。如果想成为合格软件工程师,研究下设计模式是非常必要的。

从源码分析Integer缓存机制IntegerCache和自动拆箱装箱解析_1 + 1=王的博客-程序员信息网

Integer的自动拆箱装箱解析IntegerCache自动拆箱与装箱IntegerCache自动拆箱与装箱

【夯实RabbitMQ】为什么使用消息队列?消息队列有什么优点和缺点?_小大宇的博客-程序员信息网_为什么用rabbitmq消息队列

一、系统里为什么要用消息队列这个东西? 公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。 场景有很多,但是比较核心的有 3 个:解耦、异步、削峰。 解耦:看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... 公司实际业务场景。如何同步用户信息表。...

SAP MM 史上最偷懒的盘点事务代码MI10_喜欢打酱油的老鸟的博客-程序员信息网_sap盘点事务代码

SAP MM 史上最偷懒的盘点事务代码MI10SAP标准的盘点流程是:创建盘点凭证,打印盘点凭证,录入盘点结果,最后对差异部分清账处理。这个标准流程在企业实操的时候,显得太复杂,业务人员在系统上操作工作量太大。因此SAP提供了一个异常简化系统操作的事务代码MI10,把盘点凭证创建,结果录入,差异过账核心三大步并为一步,实在是很实用!如下物料批次在工厂NM03/0001存储地下的账面库存990,假如经过盘点,我们发现该物料在工厂NM03,存储地0001下,批

随便推点

[AS/Android Studio] Gradle Plugin Version 和 Gradle Version 的版本对应关系_米歪(MiWi)的博客-程序员信息网_android gradle plugin version

Android Gradle 插件版本说明在更新 Android Studio 时,您可能会收到一并将 Gradle更新为最新可用版本的提示。您可以选择接受该更新,也可以根据项目的构建要求手动指定版本。下表列出了各个 Android Gradle 插件版本所需的 Gradle 版本。为了获得最佳性能,您应使用 Gradle和插件这两者的最新版本。插件版本所需的 Gradle 版本1.0.0 - 1.1.32.2.1 - 2.31.2.0 - 1.3.12.2.1

发帖需要验证手机-解决办法_jiuqi88的博客-程序员信息网

1、使用手机版百度APP,搜索贴吧,点击进去发帖即可(不需要绑定手机和验证手机)2、使用微信小程序也可以实现贴吧发帖,搜索贴吧小程序进去发帖(不需要绑定手机和验证手机)3、贴吧极速版APP,发帖回帖不需要验证手机(方便)应用商城或者安卓市场,搜索‘’百度APP‘’应用下载安卓的APK名称是:手机百度。苹果商城APK名字是: 百度...

layerUI api_余茕然的博客-程序员信息网_layui的api

本文档主要针对layer1.9及以上版本,如果您项目中使用的是1.9之前的版本,请前往1.8.5文档页。我们提到的基础参数主要指调用方法时用到的配置项,如:layer.open({content: ''})layer.msg('', {time: 3})等,其中的content和time即是基础参数,以键值形式存在,基础参数可合理应用于任何层类型中,您不需要所有都去配置,大多数都是可选的。而其

启明云端分享|ESP32-S3 Smartconfig一键配网_启明智显的博客-程序员信息网_esp32 一键配网

物联网时代技术开始规模化服务于民众,方便快捷显得尤为重要,smartconfig一键配网便是一个典型案例。智能家居/家电现阶段还处于普及阶段,由于家庭wifi网络的普及,目前普遍采用wifi与路由器完成连接,与手机/云端进行数据交互智能硬件,如智能插座,智能空调,智能空气净化器,智能灯泡,智能门锁由于不具备人机交互界面,不能像电脑一样的搜索/选择指定路由器,输入连接SSID和密码的界面,所以必须先解决正确连接路由问题。ESP32-S3 Smartconfig一键配网1.开发环境:esp-idf版.

基于Attention的通道知识蒸馏:channel-wise attention for knowledge distillation(论文解读+代码复现)_BIT可达鸭的博客-程序员信息网

基于Attention的通道知识蒸馏:channel-wise attention for knowledge distillation论文地址:代码地址:主要思路:具体实现:通道蒸馏:引导的知识蒸馏:衰减的蒸馏损失:实验结果:代码复现:CIFAR100Prepare DatasetTraining论文地址:https://arxiv.org/abs/2006.01683代码地址:https://github.com/zhouzaida/channel-distillation主要思路:本文提出

cs231n_assignment1_KNN_半夜萤火虫的博客-程序员信息网

KNNKNN算法实现主要涉及两个文件,一个是knn.ipynb,另一个是K_nearest_neighbor.py。实现compute_distances_two_loops def compute_distances_two_loops(self, X): #NO.1 实现 &amp;quot;&amp;quot;&amp;quot; Compute the distance between each test poi...

推荐文章

热门文章

相关标签