专栏首页Java识堂CyclicBarrier和CountDownLatch的用法与区别

CyclicBarrier和CountDownLatch的用法与区别

前言

CyclicBarrier和CountDownLatch这两个工具都是在java.util.concurrent包下,并且平时很多场景都会使用到。

本文将会对两者进行分析,记录他们的用法和区别。

CountDownLatch

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

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

特点

只能一次性使用(不能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);
    }

最终输出结果:

其中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);
    }

最终输出结果:

显然没有阻塞主线程。

两者区别

  • 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识堂

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

原始发表时间:2021-08-30
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • CyclicBarrier 和 CountDownLatch 的区别

    CountDownLatch 是计数器,只能使用一次,而 CyclicBarrier 的计数器提供 reset 功能,可以多次使用。但是我不那么认为它们之间的区...

    黑洞代码
  • CountDownLatch、CyclicBarrier和Semaphore区别及底层原理

       CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。   CountDownLa...

    程序员云帆哥
  • CountDownLatch、CyclicBarrier、Semaphore的区别,你知道吗?

    从结果可以看出,当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。

    好好学java
  • JUC源码分析之CyclicBarrier简介关键方法与参数源码解析CountDownLatch和CyclicBarrier的区别与联系应用场景小结

    JavaEdge
  • 一句话说清楚 CountDownLatch 和 CyclicBarrier 的区别

    日常工作中,经常会碰到这样的场景:有时候数据量特别大,任务量特别多,我们通常会开启多线程去分批执行任务,在所有任务执行完了之后,再去执行接下来的作业。

    kk大数据
  • java多线程并发控制countDownLatch和cyclicBarrier的使用

    java主线程等待所有子线程执行完毕在执行,这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个处理都可以用一个线...

    大道七哥
  • 高并发之——浅谈AQS中的CountDownLatch、Semaphore与CyclicBarrier

    作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了...

    冰河
  • 和学妹深入探讨CountDownLatch和CyclicBarrier的设计哲学

    最近监控发现对账系统最近越来越慢,领导急了,开始询问我能优化一下性能吗。我深入看了对账系统的业务后,发现还是挺简单的:

    JavaEdge
  • 请简要说明一下JAVA中cyclicbarrier和countdownlatch的区别分别是什么?

    CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;

    剑走天涯
  • CountDownLatch和CyclicBarrier 傻傻的分不清?超长精美图文又来了

    并发编程的三大核心是分工,同步和互斥。在日常开发中,经常会碰到需要在主线程中开启多个子线程去并行的执行任务,并且主线程需要等待所有子线程执行完毕再进行汇总的场景...

    用户4172423
  • MySQL – \g 和 \G用法与区别[通俗易懂]

    发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/112147.html原文链接:https://javaforall.cn

    全栈程序员站长
  • React.memo() 和 useMemo() 的用法与区别

    导语 | 本文翻译自 Adebola Adeniran 在 LogRocket 论坛中关于 React.memo() 和 useMemo() 对比与用例分析。 ...

    用户1097444
  • Linux中nohup与&的用法和区别详解

    我们用python代码loop_hello.py做示例,代码的作用是循环输出循环次数和hello world!,每次输出后sleep 1秒

    砸漏
  • 浅谈@RequestMapping @ResponseBody 和 @RequestBody 注解的用法与区别

    国际惯例先介绍什么是@RequestMapping,@RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有...

    全栈程序员站长
  • #if #ifdef和#ifndef的用法和区别

    移位运算符的优先级高于条件运算符,重载是不能改变运算符优先级的,这点要注意,所以代码应当像下面这样调整,写宏的时候一定要注意优先级,尽量用括号来屏蔽运算符优先级...

    狼啸风云
  • Comparable和Comparator的区别和用法

    (2)、自己写一个比较类class,实现Comparator接口并重写compare()方法

    静谧星空TEL
  • JS中== 、===的用法和区别

    薛定喵君

扫码关注腾讯云开发者

领取腾讯云代金券