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如何使用logback实现多环境配置?
Jun 16 Java/Android
Java基础之详解HashSet的使用方法
Jun 30 Java/Android
SpringBoot集成Druid连接池连接MySQL8.0.11
Jul 02 Java/Android
SpringBoot读取Resource下文件的4种方法
Jul 02 Java/Android
使用@Value值注入及配置文件组件扫描
Jul 09 Java/Android
springboot集成springCloud中gateway时启动报错的解决
Jul 16 Java/Android
idea 在springboot中使用lombok插件的方法
Aug 02 Java/Android
浅谈spring boot使用thymeleaf版本的问题
Aug 04 Java/Android
Java SSM配置文件案例详解
Aug 30 Java/Android
解析探秘fescar分布式事务实现原理
Feb 28 Java/Android
Android超详细讲解组件ScrollView的使用
Mar 31 Java/Android
Spring Boot配合PageHelper优化大表查询数据分页
Apr 20 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 和 MySQL 开发的 8 个技巧
2007/01/02 PHP
关于mysql 字段的那个点为是定界符
2007/01/15 PHP
php验证码实现代码(3种)
2015/09/07 PHP
PHP PDOStatement::execute讲解
2019/01/31 PHP
php生成word并下载代码实例
2019/03/15 PHP
php多进程并发编程防止出现僵尸进程的方法分析
2020/02/28 PHP
不错的asp中显示新闻的功能
2006/10/13 Javascript
for 循环性能比较 提高for循环的效率
2009/03/19 Javascript
一个报数游戏js版(约瑟夫环问题)
2010/08/05 Javascript
JavaScript实现的encode64加密算法实例分析
2015/04/15 Javascript
JS实现网页右侧带动画效果的伸缩窗口代码
2015/10/29 Javascript
利用 spin.js 生成等待效果(js 等待效果)
2017/06/25 Javascript
vue对storejs获取的数据进行处理时遇到的几种问题小结
2018/03/20 Javascript
js的继承方法小结(prototype、call、apply)(推荐)
2019/04/17 Javascript
分享Angular http interceptors 拦截器使用(推荐)
2019/11/10 Javascript
javascript解析json格式的数据方法详解
2020/08/07 Javascript
python启动办公软件进程(word、excel、ppt、以及wps的et、wps、wpp)
2009/04/09 Python
python paramiko利用sftp上传目录到远程的实例
2019/01/03 Python
python 实现GUI(图形用户界面)编程详解
2019/07/17 Python
Python 如何创建一个简单的REST接口
2020/07/30 Python
python3.9实现pyinstaller打包python文件成exe
2020/12/13 Python
pycharm 实现光标快速移动到括号外或行尾的操作
2021/02/05 Python
python中time tzset()函数实例用法
2021/02/18 Python
HTML5离线缓存Manifest是什么
2016/03/09 HTML / CSS
详解HTML5之pushstate、popstate操作history,无刷新改变当前url
2017/03/15 HTML / CSS
H5混合开发app如何升级的方法
2018/01/10 HTML / CSS
HTML5在手机端实现视频全屏展示方法
2020/11/23 HTML / CSS
世界上最受欢迎的钓鱼诱饵:Rapala
2019/05/02 全球购物
如何判断一段程序是由C 编译程序还是由C++编译程序编译的
2013/08/04 面试题
四风存在的原因分析
2014/02/11 职场文书
领导班子四风对照检查材料思想汇报
2014/09/26 职场文书
2015关爱留守儿童工作总结
2014/12/12 职场文书
学生会副主席竞选稿
2015/11/19 职场文书
mysql优化
2021/04/06 MySQL
聊聊golang中多个defer的执行顺序
2021/05/08 Golang
一篇文章带你深入了解Mysql触发器
2021/08/02 MySQL