CountDownLatch和CyclicBarrier区别和用法

news/2025/5/19 11:16:10

目录

1.概念解释

CountDownLatch概念:

CountDownLatch概念从源码分析:

CyclicBarrier概念

CyclicBarrier概念从源码分析

概念总结

2.构造器

3.代码验证二者在实际中的不同

4.归纳总结

5.网上的一些误区


CountDownLatch和CyclicBarrier都是juc下的并发工具类,二者功能在处理某些事情下看着很相似:都是阻塞线程,但是如果细品和查看源码的话会发现二者之间还是有区别的:

CountDownLatch主要是阻塞主线程,等待多线程执行完成之后再执行主线程await之后的代码片段,侧重点是主线程等待子线程(多线程)完成之后被唤醒。

CyclicBarrier主要是每个多线程在某一刻阻塞,然后各个多线程之间相互等待,直到最后一个多线程被阻塞,然后冲破栅栏,各自执行自己await()之后的代码段(另一个写法是用两个参数的构造去共完成另一个Runnable任务),侧重点是多线程之间的相互等待。

另外,CountDownLatch是一次性,而CyclicBarrier是可重复利用的(查看源码可以发现当最后一道栅栏被冲破之后,如果还需要用到的话会重新new Generation栅栏对象)。

了解了这两个类的侧重点之后,才更好选择合适并发工具类。下面就通过例子和源码,来验证这些区别。

1.概念解释

CountDownLatch概念:

count为计数,down减少,latch闩,所以CountDownLatch大致可以翻译为倒计时闭锁(闩)。从单词中可以很明显的得到以下信息:通过倒计时或倒数计数实现的闭锁,来实现线程的等待。

CountDownLatch概念从源码分析:

先看下该类的部分源码

     /*** Synchronization control For CountDownLatch.* Uses AQS state to represent count.*/private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}}private final Sync sync;

源码中可以看到,CountDownLatch类中有个Sync的同步类,而Sync又继承了抽象队列同步器AbstractQueuedSynchronizer,另外看到AcquireShared又应该可以猜到是基于共享锁模式。所以说CountDownLatch是基于AQS的共享锁模式。即解释了CountDownLatch是基于AQS的共享锁模式的倒计时(数)闭锁(闩)。

CyclicBarrier概念

cyclic循环,barrier栅栏,所以CyclicBarrier被翻译成循环栅栏。从单词中可以得到的信息是:通过栅栏的方式将一组线程阻塞,并且可以循环使用。

CyclicBarrier概念从源码分析

先看下该类的部分源码

    /*** Each use of the barrier is represented as a generation instance.* The generation changes whenever the barrier is tripped, or* is reset. There can be many generations associated with threads* using the barrier - due to the non-deterministic way the lock* may be allocated to waiting threads - but only one of these* can be active at a time (the one to which {@code count} applies)* and all the rest are either broken or tripped.* There need not be an active generation if there has been a break* but no subsequent reset.*/private static class Generation {boolean broken = false;}/** The lock for guarding barrier entry */private final ReentrantLock lock = new ReentrantLock();/** Condition to wait on until tripped */private final Condition trip = lock.newCondition();/** The number of parties */private final int parties;/* The command to run when tripped */private final Runnable barrierCommand;/** The current generation */private Generation generation = new Generation();/*** Number of parties still waiting. Counts down from parties to 0* on each generation.  It is reset to parties on each new* generation or when broken.*/private int count;

源码中可以看到CyclicBarrier是通过ReentrantLock和锁的Contatin来实现的(await方法调用doawait方法,里面通过ReentrantLock实现的),然后再看ReentrantLock的部分源码

    private final Sync sync;/*** Base of synchronization control for this lock. Subclassed* into fair and nonfair versions below. Uses AQS state to* represent the number of holds on the lock.*/abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/abstract void lock();/*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

可以看到ReentrantLock也含有Sync类型的变量,并且还是通过CAS操作来setExclusiveOwnerThread(current)的独占锁。所以说CyclicBarrier是基于AQS的独占锁模式。

概念总结:

CyclicBarrier是基于AQS的独占锁模式实现的阻塞线程,CoutDownLatch是基于AQS的共享锁模式阻塞线程。

2.构造器

CountDownLatch只有一个构造器public CountDownLatch(int count)

CyclicBarrier有两个构造器,分别是public CyclicBarrier(int parties, Runnable barrierAction)和public CyclicBarrier(int parties)

3.代码验证二者在实际中的不同

CountDownLatch用法

为了验证先后顺序,所以开启了两个线程并且sleep的时间不同,来模拟两个业务。这样更能看出效果

import java.util.Date;
import java.util.concurrent.CountDownLatch;public class CountDownLatchDemo {public static void main(String[] args) {CountDownLatch cdl = new CountDownLatch(2);try {for (int i = 0; i < 2; i++) {new Thread() {public void run() {long thread1Id = Thread.currentThread().getId();try {Date date1 = new Date();String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);System.out.println("线程ID:" + thread1Id + "正在执行,时间:" + szDate);int r = (int) (Math.random() * 10000);Thread.sleep(Long.parseLong(r+""));Date date2 = new Date();String szDate2 = String.format("当前时间为:%tH时%tM分%tS秒", date2, date2, date2);System.out.println("线程ID:" + thread1Id + "  over" + szDate2);cdl.countDown();System.out.println("此时计数=" + cdl.getCount());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}.start();}cdl.await();System.out.println("两个线程全部执行完成");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}

执行结果:

线程ID:10正在执行,时间:当前时间为:10时43分56秒
线程ID:11正在执行,时间:当前时间为:10时43分56秒
线程ID:11  over当前时间为:10时44分03秒
此时计数=1
线程ID:10  over当前时间为:10时44分05秒
此时计数=0
两个线程全部执行完成

结果分析

1.cdl.countDown()方法是将初始值递减。

2.主线程在调用了cdl.await();的方法之后一直在等待最后一个时间长的线程执行完成才会执行。

归纳:CountDownLatch是设置一个初始量N,每个线程中调用了countDown()方法之后,计数器变为N-1,直到计数为0的时候,调用await()的主线程才会获得锁资源执行。也就是说CountDownLatch是让一个线程(主线程)等待其他多线程全部执行完成之后再执行。

思维扩展:(感兴趣的可以自己试试)

1.在主线程调用await()的try语句块中,并且在await()之上加入一些其他的代码,会发现这些代码和两个多线程是同步执行的。也就是说只有在调用了await方法之后主线程才会被阻塞。也就是说主线程在await()下面的代码块才会被阻塞,等待获取锁。如果直接把await()方法的调用放到new CountDownLatch()之后里面执行,那惨了,后面全被阻塞了,等着去吧……

2.在多线程调用countDown()方法方法之后,再用sleep(10000),然后再sysout输出一些东西的话,会发现主线程还是会在工作时间长的多线程执行完countDown()之后会立即执行,而并不会再等10秒钟才去执行。这说明主线程cdl.getCount()等于0的时候会立马执行,不会管多线程调用countDown()之后的内容在干什么。

CyclicBarrier用法

为了验证先后顺序,所以开启了3个线程并且sleep的时间不同,来模拟三个业务。这样更能看出效果

import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {public static void main(String[] args) {CyclicBarrier cb = new CyclicBarrier(3);for (int i = 0; i < 3; i++) {new Thread() {public void run() {long thread1Id = Thread.currentThread().getId();try {Date date1 = new Date();String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);System.out.println("线程ID:" + thread1Id + "正在准备中,时间:" + szDate);int r = (int) (Math.random() * 10000);Thread.sleep(Long.parseLong(r + ""));Date date2 = new Date();String szDate2 = String.format("当前时间为:%tH时%tM分%tS秒", date2, date2, date2);System.out.println("线程ID:" + thread1Id + "  准备好了" + szDate2);try {System.out.println("线程ID:" + thread1Id + "到达栅栏,此时前面已经阻塞了的线程数" + cb.getNumberWaiting());cb.await();System.out.println("线程ID:" + thread1Id + "可以开始了");} catch (BrokenBarrierException e) {// TODO Auto-generated catch blocke.printStackTrace();}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}.start();}Date date1 = new Date();String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);System.out.println("主程序运行:" + szDate);}
}

运行结果:

主程序运行:当前时间为:10时46分12秒
线程ID:11正在准备中,时间:当前时间为:10时46分12秒
线程ID:12正在准备中,时间:当前时间为:10时46分12秒
线程ID:10正在准备中,时间:当前时间为:10时46分12秒
线程ID:12  准备好了当前时间为:10时46分12秒
线程ID:12到达栅栏,此时前面已经阻塞了的线程数0
线程ID:10  准备好了当前时间为:10时46分16秒
线程ID:10到达栅栏,此时前面已经阻塞了的线程数1
线程ID:11  准备好了当前时间为:10时46分17秒
线程ID:11到达栅栏,此时前面已经阻塞了的线程数2
线程ID:11可以开始了
线程ID:12可以开始了
线程ID:10可以开始了

结果分析

1.各线程到达栅栏的时候都会被阻塞,等待其他多线程也被阻塞

2.当所有的多线程都被阻塞之后会并发执行被await()之后的代码段

3.多线程和主线程是同时执行的,也就是说CyclicBarrier阻塞的是每个多线程,让多线程相互等待,直到所有的多线程都执行完await()之前的代码,与主线程无关。

归纳:

CyclicBarrier是用于多线程之间的相互等待,直到所有的多线程都到达阻塞的栅栏,然后所有多线程全部被唤醒,并发执行各自后面的代码。这个阻塞是与主线程无关的。

思维扩展:

上面已经说过了可以用CyclicBarrier的两个构造的方法,这样更方便各自执行完自己的操作后,共同去执行某个任务。

举个例子,3个程序员对产品设计的产品有意见,一个人说话产品觉得是你的问题,但是3个人一起去那就得跟他好好谈谈了。

import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo2 {public static void main(String[] args) {CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {public void run() {Date date1 = new Date();String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);System.out.println("既然都到了,那就一起去找产品谈谈吧,时间:" + szDate);}});for (int i = 0; i < 3; i++) {new Thread() {public void run() {long thread1Id = Thread.currentThread().getId();try {Date date1 = new Date();String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);System.out.println("程序员:" + thread1Id + "正在准备中,时间:" + szDate);int r = (int) (Math.random() * 10000);Thread.sleep(Long.parseLong(r + ""));Date date2 = new Date();String szDate2 = String.format("当前时间为:%tH时%tM分%tS秒", date2, date2, date2);System.out.println("程序员:" + thread1Id + "  准备好了" + szDate2);try {System.out.println("程序员:" + thread1Id + "到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)" + cb.getNumberWaiting());cb.await();} catch (BrokenBarrierException e) {// TODO Auto-generated catch blocke.printStackTrace();}} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}.start();}}
}

运行结果:

程序员:10正在准备中,时间:当前时间为:11时06分59秒
程序员:11正在准备中,时间:当前时间为:11时06分59秒
程序员:12正在准备中,时间:当前时间为:11时06分59秒
程序员:12  准备好了当前时间为:11时07分01秒
程序员:12到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)0
程序员:10  准备好了当前时间为:11时07分04秒
程序员:10到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)1
程序员:11  准备好了当前时间为:11时07分06秒
程序员:11到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)2
既然都到了,那就一起去找产品谈谈吧,时间:当前时间为:11时07分06秒

结果可以看出,当3个程序员都准备好开战了,就去找产品交流了。

4.归纳总结

共同点:都是阻塞线程

不同点:CountDownLatch是主线程被多线程阻塞,直到多线程执行完成才被唤醒继续执行,所以更关注主线程等待多线程执行完成再继续执行的场景;CyclicBarrier是多线程各自被阻塞在栅栏前,是多线程之间的相互等待,直到全部的多线程全部执行完成,然后并发的去共同做某件事,比如:赛跑比赛的时候,需要等所有运动员都准备完成之后,才能开始进行比赛。

所以在用的时候还需要对症下药。

5.网上的一些误区

看过一些别的文章,CountDownLatch是通过计数器递减没毛病,但是CyclicBarrier有人说递减,也有人递增,这样就很容易误导,所以碰到这种情况,还是要自己看看源码,然后动动手测试下。所以我在这里解释下为什么对于CyclicBarrier有不同的说法,其实他们都是从自己的思路出发总结的,不能说不对。

对于CyclicBarrier,内部的原理是等待所有线程被阻塞,所以对于被阻塞的线程数来说是递增的,但是源码上有这么一段

await()方法:

    public int await() throws InterruptedException, BrokenBarrierException {try {return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}}

dowait()方法:

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

意思是说每当有线程到达后,剩余没有起作用的栅栏数int index = --count(也就是说可用栅栏数执行了--操作);所以说递减也没毛病,针对当前栅栏数是递减的。

所以这里就算对迷惑的朋友或许有所帮助。

文章来源:https://blog.csdn.net/bebmwnz/article/details/107385989
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:https://dhexx.cn/news/show-17808.html

相关文章

Javascript 判断浏览器是否为IE的最短方法

作者&#xff1a;idd.chiang 发布时间&#xff1a;April 29, 2010 分类&#xff1a;Javascript/AS 在网上有幸看到夷人通过IE与非IE浏览器对垂直制表符支持特性搞出的一段简短的条件&#xff1a;var ie !"\v1"; 以上出至32 bytes, ehr ... 9, ehr ... 7!!! to know…

Python json数据中文输出问题。

这个问题困扰了我好久好久&#xff0c;最后看了一眼官方文档&#xff0c;解决问题了。 问题描述&#xff1a;从web上获取的json数据&#xff0c;然后对应的保存到了python的类型中。再次输出这个数据时&#xff0c;中文总会变成\u1234这种形式。 Python版本&#xff1a;2.7 解决…

hue修改oozie的workflow工作流和Schedules任务,修改不生效的解决办法

在hue可视化界面&#xff0c;编辑oozie的workflow工作流和Schedules任务&#xff0c;界面上明明已经添加了某个子workflow&#xff0c;或添加了shell命令任务&#xff0c;或是明明删除了某个节点&#xff0c;因为某些原因没有生效&#xff0c;导致报错的解决办法直接修改此工作…

大话24种设计模式详解及实现过程详解

先来个抛砖引玉&#xff1a;大家在开发中经常碰到很多if&#xff08;&#xff09;else{}条件的判断&#xff0c;其实这也算是一种设计模式--策略模式。当然&#xff0c;如果业务已经固定&#xff0c;后期不需要做任何修改的话&#xff0c;那么这种硬编码格式的确不会遇到任何问…

Linux 性能监测:CPU

CPU 的占用主要取决于什么样的资源正在 CPU 上面运行&#xff0c;比如拷贝一个文件通常占用较少 CPU&#xff0c;因为大部分工作是由 DMA&#xff08;Direct Memory Access&#xff09;完成&#xff0c;只是在完成拷贝以后给一个中断让 CPU 知道拷贝已经完成&#xff1b;科学计…

TSQL--逻辑查询处理

1. 查询处理可分成逻辑处理和物理处理&#xff0c;逻辑处理上各阶段有特定的顺序&#xff0c;但为优化查询&#xff0c;在保证结果集正确的条件下&#xff0c;物理处理顺序并不按照逻辑处理顺序执行&#xff0c;如果在INNER JOIN时&#xff0c;WHERE语句中的过滤条件会在INNER …

hue调度shell脚本,shell脚本操作hive表,shell脚本里用spark-submit 调用java程序 [生产环境使用]

executeAllConvert.sh &#xff0c;hue调用此shell文件&#xff0c;这个文件是调用的入口文件 #!/bin/sh -ldir$(cd "$(dirname "$0")";pwd) source ./env.confspark-submit --keytab /var/lib/hadoop-hdfs/hdfs.keytab --principal hdfs/hdfsKIUKIANG.CO…

设计模式之代理模式Proxy Pattern

名词解释&#xff1a; 所谓代理模式就是用一个类B去完成类A的功能&#xff0c;说白了就是给类A找个代理B&#xff0c;来完成A的功能。举个例子&#xff1a;春节放假&#xff0c;异地工作的你回家过年需要买票&#xff0c;如果去车站买票&#xff0c;但是你时间不允许&#xff…

git diff输出信息的含义

问题&#xff1a;使用git diff命令输出信息那些符号是什么意思&#xff1f; 版本管理系统git&#xff0c;使用的是合并格式diff的变体。   $ git diff 显示结果如下&#xff1a;   diff --git a/f1 b/f1   index 6f8a38c..449b072 100644   --- a/f1 …

Android开发系列(二) 布局资源Layout的使用

熟练掌握以下重要属性&#xff0c;并灵活运用&#xff1a; android:layout_centerInParent   居中布局      android:layout_centerVertical    垂直居中布局      android:layout_centerHorizontal  水平居中布局 android:layout_alignParentTop    居于容…