多线程并发之CountDownLatch(闭锁)使用详解

article/2025/10/4 10:50:15

专题相关文章:

从内存可见性看Volatile、原子变量和CAS算法
多线程并发之CountDownLatch(闭锁)使用详解
多线程并发之显示锁Lock与其通信方式Condition源码解读
多线程并发之读写锁(ReentranReadWriteLock&ReadWriteLock)使用详解
多线程并发之线程池Executor与Fork/Join框架
多线程并发之JUC 中的 Atomic 原子类总结
多线程并发之volatile的底层实现原理
多线程并发之Semaphore(信号量)使用详解

【1】CountDownLatch是什么

CountDownLatch,英文翻译为倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:

  • 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
  • 等待直到某个操作所有参与者都准备就绪再继续执行。

CountDownLatch有一个正数计数器,countDown()方法对计数器做减操作,await()方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时。

闭锁(倒计时锁)主要用来保证完成某个任务的先决条件满足。是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。

CountDownLatch同样依赖队列同步器AbstractQueuedSynchronizer,其类方法如下:
**加粗样式**
其内部类Sync同样继承了AQS并重写了tryAcquireShared和tryReleaseShared方法。同时也可以表明CountDownLatch是基于共享锁模式的。


CountDownLatch 的两种典型用法

①某一线程在开始运行前等待n个线程执行完毕。

将 CountDownLatch 的计数器初始化为n :new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

②实现多个线程开始执行任务的最大并行性。

注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

如下示例,在多线程运行情况下,计算多线程耗费时间:

public class TestCountDownLatch {public static void main(String[] args){LatchDemo latchDemo = new LatchDemo();long begin = System.currentTimeMillis();//多线程for (int i = 0; i <5 ; i++) {new Thread(latchDemo).start();}//主线程long end = System.currentTimeMillis();System.out.println("耗费时间:"+(end-begin));}
}class LatchDemo implements  Runnable{@Overridepublic void run() {for (int i = 0; i < 50000; i++) {if (i%2==0){System.out.println(i);}}}
}

如上示例,很显然让不能计算出多线程运行的时间!!这时,就可以使用 闭锁解决这个问题。


【2】CountDownLatch源码分析与使用

① 修改【1】中代码如下:

public class TestCountDownLatch {public static void main(String[] args){//CountDownLatch 为唯一的、共享的资源final CountDownLatch latch = new CountDownLatch(5);LatchDemo latchDemo = new LatchDemo(latch);long begin = System.currentTimeMillis();for (int i = 0; i <5 ; i++) {new Thread(latchDemo).start();}try {//多线程运行结束前一直等待latch.await();} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("耗费时间:"+(end-begin));}
}class LatchDemo implements  Runnable{private CountDownLatch latch;public LatchDemo(CountDownLatch latch){this.latch=latch;}public LatchDemo(){super();}@Overridepublic void run() {//当前对象唯一,使用当前对象加锁,避免多线程问题synchronized (this){try {for (int i = 0; i < 50000; i++) {if (i%2==0){System.out.println(i);}}}finally {//保证肯定执行latch.countDown();}}}
}

测试结果如下图:

这里写图片描述


② CountDownLatch源码如下:

package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;public class CountDownLatch {/*** CountDownLatch的同步控制,同样依赖使用AQS,使用AQS state表示count计数*/private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {//设置初始计数setState(count);}int getCount() {//获取当前计数return getState();}//这里重写了AQS的tryAcquireShared方法,小于0表示count>0,可以获取。protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {//count-1,如果count变为0,则唤醒所有for (;;) {//获取当前状态,为0表示未锁,不用释放int c = getState();if (c == 0)return false;int nextc = c-1;//使用CAS算法,c为期望值,nextc为更新值if (compareAndSetState(c, nextc))return nextc == 0;}}}private final Sync sync;/**
使用给定的count构造CountDownLatch,count表示线程通过await前必须要执行的次数,count不能小于0*/public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);}/**让当前线程等待直到count减数为0,除非线程被中断。如果count为0,线程将立即返回--不再阻塞等待。如果当前计数大于零,则出于线程调度目的,当前线程将禁用,并处于休眠状态,直到发生以下两种情况之一:1.countDown方法调用导致count减数为0;2.别的线程中断了当前线程* 线程等待时,如果被中断将会抛出InterruptedException异常*/public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}使当前线程处理等待状态直到count减为0或者指定等待时间过去。
如果当前count是0,则线程立即返回true。
如果当前计数大于零,则出于线程调度目的,当前线程将禁用,并处于休眠状态,直到发生以下三种情况之一:1.countDown方法调用导致count减数为0;2.别的线程中断了当前线程3.指定等待时间过去   
如果等待时间过去但是count>0,则返回false。如果等待时间时间小于或等于零,方法将不会等待。public boolean await(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}/*** 将count-1 ,如果count减一后为0 ,则释放所有等待的线程*/public void countDown() {sync.releaseShared(1);}/**返回当前的count,该方法通常用在debug或者测试中*/public long getCount() {return sync.getCount();}public String toString() {return super.toString() + "[Count = " + sync.getCount() + "]";}
}

简单总结如下:

  • 内部类Sync同样继承AQS;
  • AQS的state代表count;
  • 初始化使用计数器count;
  • count代表多个线程执行或者某个操作执行次数;
  • countDown()方法将会将count-1;
  • count为0将会释放所有等待线程;
  • await方法将会阻塞直到count为0;
  • CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
  • count不为0,但是等待时间过去将会返回false。
  • 开关锁应用;
  • 问题分解应用–并行性;

③ CountDownLatch Javadoc

一个同步助手使一个或多个线程在其他线程完成一系列操作前一直处于等待状态。CountDownLatch使用一个count初始化。await方法将会使线程阻塞直到由于countDown方法导致count计数达到0。如果count减为0,所有等到的线程将会被释放并立即返回到后续调用。这是一次性的现象,不能重置计数。如果需要重新设置count,可以使用CyclicBarrier–栅栏。参考博文:多线程并发之CyclicBarrier(栅栏)使用详解

CountDownLatch是一个多能力的同步工具可以被使用到许多目的。

  • 使用 one 作为count值初始化的CountDownLatch可以被用作开关锁或着门:所有线程在这个门前等待(await方法)直到某个线程调用countDown将门打开。
  • 使用N作为count值初始化的CountDownLatch可以被用于使某个线程等待N个线程完成某个动作或者某个动作被完成N次。

CountDownLatch的一个有用特性是,它不要求调用countDown的线程在继续之前等待计数达到零,它只是防止任何线程通过直到所有线程都可以通过。


④ 官方实例一

这里有两个类,其中一组worker使用两个倒计时锁存器:第一个锁是一个开始信号–阻止任何worker直到driver准备好;第二个锁是一个完成信号–允许driver等待直到所有的worker都完成。

class Driver { // ...void main() throws InterruptedException {CountDownLatch startSignal = new CountDownLatch(1);CountDownLatch doneSignal = new CountDownLatch(N);for (int i = 0; i < N; ++i) // create and start threadsnew Thread(new Worker(startSignal, doneSignal)).start();doSomethingElse();            // don't let run yetstartSignal.countDown();      // let all threads proceeddoSomethingElse();doneSignal.await();           // wait for all to finish}}class Worker implements Runnable {private final CountDownLatch startSignal;private final CountDownLatch doneSignal;Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {this.startSignal = startSignal;this.doneSignal = doneSignal;}public void run() {try {startSignal.await();doWork();doneSignal.countDown();} catch (InterruptedException ex) {} // return;}void doWork() { ... }}}

⑤ 官方实例二
另一个典型的用法是将问题分成N个部分,用Runnable描述每个部分,Runnable执行该部分并在锁存器上倒计时,并将所有Runnable排队给Executor。当所有子部件完成时,协调线程将能够通过等待(如果需要使用这种形式,建议使用CyclicBarrier)。

	class Driver2 { // ...void main() throws InterruptedException {CountDownLatch doneSignal = new CountDownLatch(N);Executor e = Executors.newScheduledThreadPool(N)for (int i = 0; i < N; ++i) // create and start threadse.execute(new WorkerRunnable(doneSignal, i));doneSignal.await();           // wait for all to finish}}class WorkerRunnable implements Runnable {private final CountDownLatch doneSignal;private final int i;WorkerRunnable(CountDownLatch doneSignal, int i) {this.doneSignal = doneSignal;this.i = i;}public void run() {try {doWork(i);doneSignal.countDown();} catch (InterruptedException ex) {} // return;}void doWork() { ... }}}

【3】CountDownLatch中Sync使用AQS的相关方法

① await()方法中的acquireSharedInterruptibly(1)

 public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}

AQS中acquireSharedInterruptibly(1)源码如下:

 /*** Acquires in shared mode, aborting if interrupted.  Implemented* by first checking interrupt status, then invoking at least once* {@link #tryAcquireShared}, returning on success.  Otherwise the* thread is queued, possibly repeatedly blocking and unblocking,* invoking {@link #tryAcquireShared} until success or the thread* is interrupted.* @param arg the acquire argument.* This value is conveyed to {@link #tryAcquireShared} but is* otherwise uninterpreted and can represent anything* you like.* @throws InterruptedException if the current thread is interrupted*/public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//如果小于0,就执行获取操作if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

② await(long timeout, TimeUnit unit)中的tryAcquireSharedNanos(1, unit.toNanos(timeout))

public boolean await(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}

AQS中tryAcquireSharedNanos(int arg, long nanosTimeout)源码如下:

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//很有意思,这里用了 ||return tryAcquireShared(arg) >= 0 ||doAcquireSharedNanos(arg, nanosTimeout);}

③ countDown()中的releaseShared(int arg)

public void countDown() {sync.releaseShared(1);
}

AQS中releaseShared(int arg)源码如下:

 public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}

http://chatgpt.dhexx.cn/article/pYU8LRm1.shtml

相关文章

【ABAQUS】什么是剪切闭锁?剪切闭锁会导致什么?

“完全积分”是指当单元具有规则形状时&#xff0c;对单元刚度矩阵中的多项式项进行精确积分所需的高斯点数。对于六面体和四边形元素&#xff0c;“规则形状”意味着边缘是直的&#xff0c;并以直角相交&#xff0c;任何边缘节点都位于边缘的中点。 完全积分的线性元素在每个…

电力“五防”闭锁系统

&#xff08;转载&#xff09; 电力“五防”闭锁系统 随着电子技术在各个行业领域的飞速发展&#xff0c;电力的“五防”也将发生革命性的转变。现代电力系统规模不断扩大&#xff0c;从而促使电压的等级也得到了进一步提高。为了使电力系统的安全稳定运行能够得以充分保障&am…

【LB-1A 100V电压回路断相闭锁继电器】

系列型号 LB-1A电压回路断相闭锁继电器 LB-1A闭锁继电器 1 用途 LB-1A型电压回路断相闭锁继电器&#xff08;以下简称继电器&#xff09;是在交流电压回路断线而可能引起继电保护误动作时&#xff0c;对继电保护进行闭锁。该继电器用于中性点非直接接地系统中。 2 结构与工作原…

间隙锁

一.几个基本概念 行锁&#xff1a;给某一行加的锁间隙锁&#xff1a;就是两个值之间的间隙。为了解决幻读问题&#xff0c;InnoDB 只好引入新的锁&#xff0c;也就是 间隙锁 (Gap Lock)。间隙锁Gap,左右都是开区间&#xff0c;间隙锁行锁合称next-key lock,每个 next-key lock…

闭锁,信号量,栅栏

1. 闭锁&#xff08;countDownLatch&#xff09; 1.1. 作用&#xff1a; 相当于一扇门&#xff0c;在闭锁到达结束状态之前&#xff0c;这扇门是关着的&#xff0c;所以的线程都不允许通过&#xff0c;当闭锁到达结束状态&#xff0c;这扇门打开并允许所有的线程通过。在…

visio 2010激活教程

一、下载office2010toolkit.zip 若下载链接失效&#xff0c;手动搜索office2010toolkit http://ys-c.ys168.com/605279628/o4W138W45JIPI5SiuWf5/office2010toolkit.zip二、激活 激活过程中需要关闭office套件

Microsoft Visio Professional 2013 安装步骤

1.打开解压后的文件夹&#xff0c;点击setup.exe安装 2.勾选我接受此协议的条款&#xff0c;继续 3.点击立即安装 4.安装完成&#xff0c;关闭。 5.打开第一步解压的文件夹中破解文件夹&#xff0c;鼠标右键以管理员身份运行 6.点击激活office2013VL 7.激活完成 Microsoft Visi…

激活Visio2013

最近需要用Visio&#xff0c;由于密钥过期了&#xff0c;也不知道怎么重新输密钥&#xff0c;于是网上搜索&#xff0c;终于找到了一个方法。 KMSpico工具&#xff0c;安装完后直接打开点击左边红色按钮就可以了。如图&#xff1a; 最后附上工具链接https://pan.baidu.com/s/1…

机器学习十大算法之-CART分类决策树、回归树和模型树

转载&#xff08;http://blog.163.com/zhoulili1987619126/blog/static/35308201201542731455261/&#xff09; Classification And Regression Tree(CART)是决策树的一种&#xff0c;并且是非常重要的决策树&#xff0c;属于Top Ten Machine Learning Algorithm。顾名思义&…

决策树入门以及树模型的参数选择

决策树及树模型的参数选择 决策树的基本概念 在数据结构中树是一个重要的数据结构&#xff0c;这里树被我们根据分支起到一个决策的作用。什么是决策&#xff1f;通俗的说就是判断或者决定&#xff0c;我们引用周志华的西瓜书中的例子&#xff1a;这是一个好瓜吗&#xff1f;当…

gbdt、xgb、lgb决策树模型

目录 1.决策树1.1 CART分类树1.2 CART回归树 2.gbdt3.xgboost4.lightgbm5.模型对比6.参考文献 本文主要对决策树、gbdt、xgboost、lightgbm、catboost进行简述和整理&#xff0c;包括模型原理、优劣性等内容 1.决策树 决策树是一种通过对历史数据进行测算&#xff0c;对新数据…

树模型与线性模型的融合模型(Python实现)

目录 一、树模型与线性模型的融合模型 二、Python sklearn实现GBDTLR融合模型 一、树模型与线性模型的融合模型 树模型GBDT原理&#xff1a;https://blog.csdn.net/woniu201411/article/details/83114226 线性模型LR原理&#xff1a;https://blog.csdn.net/woniu201411/art…

机器学习理论与实战(九)回归树和模型树

前一节的回归是一种全局回归模型,它设定了一个模型,不管是线性还是非线性的模型,然后拟合数据得到参数,现实中会有些数据很复杂,肉眼几乎看不出符合那种模型,因此构建全局的模型就有点不合适。这节介绍的树回归就是为了解决这类问题,它通过构建决策节点把数据数据切分成…

决策树模型

本文代码及数据集来自《Python大数据分析与机器学习商业案例实战》 决策树模型的建树依据主要用到的是基尼系数的概念。基尼系数&#xff08;gini&#xff09;用于计算一个系统中的失序现象&#xff0c;即系统的混乱程度。基尼系数越高&#xff0c;系统的混乱程度就越高&#x…

新版HyperMesh的Assemblies中调出下拉菜单查看Component(装配关系模型树)

新版HyperMesh的Assemblies中调出下拉菜单查看Component&#xff08;装配关系模型树&#xff09; 从Altair更新的2019版本开始&#xff0c;在Model模型树中找不到Assemblies了&#xff0c;这样导致好多用户一直在用14或者17版本&#xff0c;其实这次更新不是把这个功能取消掉了…

Cesium bim模型加载并与模型树关联(分层加载)

很久都没写文章了&#xff0c;最近实在是太忙了&#xff0c;向各位关注我的小伙伴致个歉&#xff01; 最近没事儿写了个模型树和模型关联的功能&#xff0c;处理工具是用的cesiumlab。 说明一下为什么要用cesiumlab&#xff1a; 网上现在有很多的模型转换工具&#xff0c;如…

举例讲清楚模型树和回归树的区别

根据上面图中的一个例子&#xff0c;我们来分析下&#xff0c;假设A、B两点是曲线上的噪声点。 模型树的叶子节点是一个分段线性函数[1]。 回归树的叶子节点是取一个区间的平均(一个常数值)。 上述图中AB区间的均值是一个暗点。 所以&#xff1a; 对于节点A而言&#xff0c;模…

[Ansys Workbench] Mechanical 界面显示模型树窗口和详细信息窗口

根据这个回答才找到的……我之前也没注意&#xff0c;别人问我我才去想的 https://forum.ansys.com/discussion/8740/workbench-tree-outline 一般的方法是在 主页 - 布局 - 管理 里面选择显示哪个视窗 如果就是想显示模型树窗口和详细信息窗口&#xff0c;可以使用快捷键 Ctr…

creo文件如何检入服务器,CREO模型树配置的保存与检索

保存模型树配置 (1)单击 然后单击“保存设置文件”(Save Settings File)。“保存模型树配置”(Save Model Tree Configuration) 对话框打开。 (2)接受默认文件名 tree.cfg 或键入新名称。 (3)单击“保存”(Save)。 手动检索模型树配置 (1)单击 然后单击“打开设置文件”(Open S…

Graphviz绘制模型树2——XGBoost模型的可解释性

从二分类模型中的树重新理解XGBoost算法 一.对绘制的树简单解释1.1类别判断1.2树的最大层级1.3效果较差情况 二.从数据来解释一棵树2.1EXCEL构建第1颗树2.2第1棵树的数据解释2.3效果较差的节点解释 三.N颗树如何预测样本3.1样本22数据3.2样本22落入叶子情况3.3样本22的总结 四.…