Java中CyclicBarrier和CountDownLatch的用法与区别


Posted in Java/Android onAugust 23, 2021

前言

CyclicBarrier和CountDownLatch这两个工具都是在java.util.concurrent包下,并且平时很多场景都会使用到。
本文将会对两者进行分析,记录他们的用法和区别。

CountDownLatch

CountDownLatch是一个非常实用的多线程控制工具类,称之为“倒计时器”,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

Java中CyclicBarrier和CountDownLatch的用法与区别

特点

只能一次性使用(不能reset);主线程阻塞;某个线程中断将永远到不了屏障点,所有线程都会一直等待。

例子

//创建初始化3个线程的线程池
    private ExecutorService                    threadPool     = Executors.newFixedThreadPool(3);
    //保存每个学生的平均成绩
    private ConcurrentHashMap<String, Integer> map            = new ConcurrentHashMap<>();
    private CountDownLatch                     countDownLatch = new CountDownLatch(3);

    private void count() {
        for (int i = 0; i < 3; i++) {
            threadPool.execute(() -> {
                //计算每个学生的平均成绩,代码略()假设为60~100的随机数
                int score = (int) (Math.random() * 40 + 60);
                try {
                    Thread.sleep(Math.round(Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                map.put(Thread.currentThread().getName(), score);
                System.out.println(Thread.currentThread().getName() + "同学的平均成绩为" + score);
                countDownLatch.countDown();
            });
        }
        this.run();
        threadPool.shutdown();
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int result = 0;
        Set<String> set = map.keySet();
        for (String s : set) {
            result += map.get(s);
        }
        System.out.println("三人平均成绩为:" + (result / 3) + "分");
    }

    public static void main(String[] args) throws InterruptedException {
        long now = System.currentTimeMillis();
        CyclicBarrier1 cb = new CyclicBarrier1();
        cb.count();
        Thread.sleep(100);
        long end = System.currentTimeMillis();
        System.out.println(end - now);
    }

最终输出结果:

Java中CyclicBarrier和CountDownLatch的用法与区别

其中1194ms证明了会阻塞主线程。

CyclicBarrier

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的(reset()方法重置屏障点),这一点与CountDownLatch不同。

CyclicBarrier是一种同步机制允许一组线程相互等待,等到所有线程都到达一个屏障点才退出await方法,它没有直接实现AQS而是借助ReentrantLock来实现的同步机制。它是可循环使用的,而CountDownLatch是一次性的,另外它体现的语义也跟CountDownLatch不同,CountDownLatch减少计数到达条件采用的是release方式,而CyclicBarrier走向屏障点(await)采用的是Acquire方式,Acquire是会阻塞的,这也实现了CyclicBarrier的另外一个特点,只要有一个线程中断那么屏障点就被打破,所有线程都将被唤醒(CyclicBarrier自己负责这部分实现,不是由AQS调度的),这样也避免了因为一个线程中断引起永远不能到达屏障点而导致其他线程一直等待。屏障点被打破的CyclicBarrier将不可再使用(会抛出BrokenBarrierException)除非执行reset操作。

构造函数

CyclicBarrier有两个构造函数:

CyclicBarrier(int parties)
int类型的参数表示有几个线程来参与这个屏障拦截,(拿上面的例子,即有几个人跟团旅游);

CyclicBarrier(int parties,Runnable barrierAction)
当所有线程到达一个屏障点时,优先执行barrierAction这个线程。

最重要的一个方法:
await();每个线程调用await(),表示我已经到达屏障点,然后当前线程被阻塞。

例子

//创建初始化3个线程的线程池
    private ExecutorService                    threadPool     = Executors.newFixedThreadPool(3);
    //创建3个CyclicBarrier对象,执行完后执行当前类的run方法
    private CyclicBarrier                      cb             = new CyclicBarrier(3, this);
    //保存每个学生的平均成绩
    private ConcurrentHashMap<String, Integer> map            = new ConcurrentHashMap<>();

    private void count() {
        for (int i = 0; i < 3; i++) {
            threadPool.execute(() -> {
                //计算每个学生的平均成绩,代码略()假设为60~100的随机数
                int score = (int) (Math.random() * 40 + 60);
                try {
                    Thread.sleep(Math.round(Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                map.put(Thread.currentThread().getName(), score);
                System.out.println(Thread.currentThread().getName() + "同学的平均成绩为" + score);
                try {
                    //执行完运行await(),等待所有学生平均成绩都计算完毕
                    cb.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
        threadPool.shutdown();
    }

    @Override
    public void run() {
        int result = 0;
        Set<String> set = map.keySet();
        for (String s : set) {
            result += map.get(s);
        }
        System.out.println("三人平均成绩为:" + (result / 3) + "分");
    }

    public static void main(String[] args) throws InterruptedException {
        long now = System.currentTimeMillis();
        CyclicBarrier1 cb = new CyclicBarrier1();
        cb.count();
        Thread.sleep(100);
        long end = System.currentTimeMillis();
        System.out.println(end - now);
    }

最终输出结果:

Java中CyclicBarrier和CountDownLatch的用法与区别

显然没有阻塞主线程。

两者区别

  • CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()
  • 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
  • CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。
  • CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
  • 某线程中断CyclicBarrier会抛出异常,避免了所有线程无限等待。

我们来从jdk作者设计的目的来看,javadoc是这么描述它们的:

CountDownLatch:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

CyclicBarrier:
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.

从javadoc的描述可以得出:

CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。
对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。

到此这篇关于Java中CyclicBarrier和CountDownLatch的用法与区别的文章就介绍到这了,更多相关Java CyclicBarrier CountDownLatch内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
SpringBoot实现异步事件驱动的方法
Jun 28 Java/Android
一篇文章带你学习Mybatis-Plus(新手入门)
Aug 02 Java/Android
Java中CyclicBarrier和CountDownLatch的用法与区别
Aug 23 Java/Android
logback如何自定义日志存储
Aug 30 Java/Android
spring cloud 配置中心客户端启动遇到的问题
Sep 25 Java/Android
Spring Cloud 中@FeignClient注解中的contextId属性详解
Sep 25 Java/Android
java executor包参数处理功能 
Feb 15 Java/Android
RestTemplate如何通过HTTP Basic Auth认证示例说明
Mar 17 Java/Android
Spring依赖注入多种类型数据的示例代码
Mar 31 Java/Android
springboot应用服务启动事件的监听实现
Apr 06 Java/Android
详解Flutter自定义应用程序内键盘的实现方法
Jun 14 Java/Android
OpenFeign实现远程调用
Aug 14 Java/Android
SpringBoot整合Mybatis Generator自动生成代码
Aug 23 #Java/Android
Java面试题冲刺第十九天--数据库(4)
Java获取e.printStackTrace()打印的信息方式
Aug 07 #Java/Android
Java移除无效括号的方法实现
Aug 07 #Java/Android
简述Java中throw-throws异常抛出
Aug 07 #Java/Android
Java比较两个对象中全部属性值是否相等的方法
Aug 07 #Java/Android
Java org.w3c.dom.Document 类方法引用报错
Aug 07 #Java/Android
You might like
php地址引用(php地址引用的效率问题)
2012/03/23 PHP
探讨PHP函数ip2long转换IP时数值太大产生负数的解决方法
2013/06/06 PHP
php基于jquery的ajax技术传递json数据简单实例
2016/04/15 PHP
php版阿里云OSS图片上传类详解
2016/12/01 PHP
Laravel 读取 config 下的数据方法
2019/10/13 PHP
JavaScript 学习笔记(十四) 正则表达式
2010/01/22 Javascript
javascript中的prototype属性实例分析说明
2010/08/09 Javascript
js导出table数据到excel即导出为EXCEL文档的方法
2013/10/10 Javascript
JS控制图片翻转示例代码(兼容firefox,ie,chrome)
2013/12/19 Javascript
jQuery的$.proxy()应用示例介绍
2014/04/03 Javascript
JavaScript加入收藏夹功能(兼容IE、firefox、chrome)
2014/05/05 Javascript
Javascript中的call()方法介绍
2015/03/15 Javascript
jQuery图片轮播实现并封装(一)
2016/12/03 Javascript
js数组去重的hash方法
2016/12/22 Javascript
Bootstrap页面标题Page Header的实现方法
2017/03/22 Javascript
JQuery.dataTables表格插件添加跳转到指定页
2017/06/09 jQuery
element vue Array数组和Map对象的添加与删除操作
2018/11/14 Javascript
解决微信小程序调用moveToLocation失效问题【超简单】
2019/04/12 Javascript
js核心基础之闭包的应用实例分析
2019/05/11 Javascript
原生js实现无缝轮播图效果
2021/01/28 Javascript
[38:44]DOTA2上海特级锦标赛A组小组赛#2 Secret VS CDEC第二局
2016/02/25 DOTA
[03:02]生活中的Dendi之野外度假篇
2016/08/09 DOTA
python数字图像处理实现直方图与均衡化
2018/05/04 Python
Python 经典面试题 21 道【不可错过】
2018/09/21 Python
Python UnboundLocalError和NameError错误根源案例解析
2018/10/31 Python
python剪切视频与合并视频的实现
2020/03/03 Python
Python如何实现的二分查找算法
2020/05/27 Python
使用Python解析Chrome浏览器书签的示例
2020/11/13 Python
CSS3动画animation实现云彩向左滚动
2014/05/09 HTML / CSS
CSS中越界问题的经典解决方案【推荐】
2016/04/19 HTML / CSS
HTML5 通过Vedio标签实现视频循环播放的示例代码
2020/08/05 HTML / CSS
Engel & Bengel官网:婴儿推车、儿童房家具和婴儿设备
2019/12/28 全球购物
大学生四年生活自我鉴定
2013/11/21 职场文书
教育技术职业规划范文
2014/03/04 职场文书
优秀大学生求职自荐信范文
2014/04/19 职场文书
GPU服务器的多用户配置方法
2022/07/07 Servers