Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理
发布时间 - 2026-01-11 01:51:33 点击率:次CountDownLatch简介

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
CountDownLatch和CyclicBarrier的区别
(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
关于CyclicBarrier的原理,后面一章再来学习。
CountDownLatch函数列表
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 void await() // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 boolean await(long timeout, TimeUnit unit) // 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 void countDown() // 返回当前计数。 long getCount() // 返回标识此锁存器及其状态的字符串。 String toString()
CountDownLatch数据结构
CountDownLatch的UML类图如下:
CountDownLatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。
1. CountDownLatch(int count)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
说明:该函数是创建一个Sync对象,而Sync是继承于AQS类。Sync构造函数如下:
Sync(int count) {
setState(count);
}
setState()在AQS中实现,源码如下:
protected final void setState(long newState) {
state = newState;
}
说明:在AQS中,state是一个private volatile long类型的对象。对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。
2. await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
说明:该函数实际上是调用的AQS的acquireSharedInterruptibly(1);
AQS中的acquireSharedInterruptibly()的源码如下:
public final void acquireSharedInterruptibly(long arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
说明:acquireSharedInterruptibly()的作用是获取共享锁。
如果当前线程是中断状态,则抛出异常InterruptedException。否则,调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。
tryAcquireShared()在CountDownLatch.java中被重写,它的源码如下:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
说明:tryAcquireShared()的作用是尝试获取共享锁。
如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。
private void doAcquireSharedInterruptibly(long arg)
throws InterruptedException {
// 创建"当前线程"的Node节点,且Node中记录的锁是"共享锁"类型;并将该节点添加到CLH队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取上一个节点。
// 如果上一节点是CLH队列的表头,则"尝试获取共享锁"。
final Node p = node.predecessor();
if (p == head) {
long r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// (上一节点不是CLH队列的表头) 当前线程一直等待,直到获取到共享锁。
// 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
说明:
(01) addWaiter(Node.SHARED)的作用是,创建”当前线程“的Node节点,且Node中记录的锁的类型是”共享锁“(Node.SHARED);并将该节点添加到CLH队列末尾。
(02) node.predecessor()的作用是,获取上一个节点。如果上一节点是CLH队列的表头,则”尝试获取共享锁“。
(03) shouldParkAfterFailedAcquire()的作用和它的名称一样,如果在尝试获取锁失败之后,线程应该等待,则返回true;否则,返回false。
(04) 当shouldParkAfterFailedAcquire()返回ture时,则调用parkAndCheckInterrupt(),当前线程会进入等待状态,直到获取到共享锁才继续运行。
3. countDown()
public void countDown() {
sync.releaseShared(1);
}
说明:该函数实际上调用releaseShared(1)释放共享锁。
releaseShared()在AQS中实现,源码如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。
它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。
tryReleaseShared()在CountDownLatch.java中被重写,源码如下:
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
// 获取“锁计数器”的状态
int c = getState();
if (c == 0)
return false;
// “锁计数器”-1
int nextc = c-1;
// 通过CAS函数进行赋值。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
说明:tryReleaseShared()的作用是释放共享锁,将“锁计数器”的值-1。
总结:CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!
以上,就是CountDownLatch的实现原理。
CountDownLatch的使用示例
下面通过CountDownLatch实现:"主线程"等待"5个子线程"全部都完成"指定的工作(休眠1000ms)"之后,再继续运行。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
public class CountDownLatchTest1 {
private static int LATCH_SIZE = 5;
private static CountDownLatch doneSignal;
public static void main(String[] args) {
try {
doneSignal = new CountDownLatch(LATCH_SIZE);
// 新建5个任务
for(int i=0; i<LATCH_SIZE; i++)
new InnerThread().start();
System.out.println("main await begin.");
// "主线程"等待线程池中5个任务的完成
doneSignal.await();
System.out.println("main await finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class InnerThread extends Thread{
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
// 将CountDownLatch的数值减1
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
main await begin. Thread-0 sleep 1000ms. Thread-2 sleep 1000ms. Thread-1 sleep 1000ms. Thread-4 sleep 1000ms. Thread-3 sleep 1000ms. main await finished.
结果说明:主线程通过doneSignal.await()等待其它线程将doneSignal递减至0。其它的5个InnerThread线程,每一个都通过doneSignal.countDown()将doneSignal的值减1;当doneSignal为0时,main被唤醒后继续执行。
# java
# concurrency
# countdownlatch原理和示例
# 详解java CountDownLatch和CyclicBarrier在内部实现和场景上的区别
# Java线程并发工具类CountDownLatch原理及用法
# JAVA CountDownLatch(倒计时计数器)用法实例
# 浅谈java并发之计数器CountDownLatch
# java使用CountDownLatch等待多线程全部执行完成
# Java并发系列之CountDownLatch源码分析
# JAVA多线程CountDownLatch使用详解
# Java CountDownLatch应用场景代码实例
# 上一
# 是一个
# 数据结构
# 重写
# 将该
# 值为
# 多个
# 则是
# 它是
# 很简单
# 再来
# 被称为
# 会使
# 所持
# 在等待
# 创建一个
# 抛出
# 一章
# 中时
# 过程中
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
PHP怎么接收前端传的文件路径_处理文件路径参数接收方法【汇总】
谷歌浏览器如何更改浏览器主题 Google Chrome主题设置教程
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
如何在宝塔面板中修改默认建站目录?
如何用低价快速搭建高质量网站?
php485函数参数是什么意思_php485各参数详细说明【介绍】
Laravel中间件如何使用_Laravel自定义中间件实现权限控制
Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制
Laravel如何集成微信支付SDK_Laravel使用yansongda-pay实现扫码支付【实战】
Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤
如何快速搭建高效WAP手机网站吸引移动用户?
如何在沈阳梯子盘古建站优化SEO排名与功能模块?
laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程
打开php文件提示内存不足_怎么调整php内存限制【解决方案】
php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】
Laravel怎么使用Markdown渲染文档_Laravel将Markdown内容转HTML页面展示【实战】
EditPlus 正则表达式 实战(3)
奇安信“盘古石”团队突破 iOS 26.1 提权
如何获取免费开源的自助建站系统源码?
高配服务器限时抢购:企业级配置与回收服务一站式优惠方案
哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?
Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】
Laravel定时任务怎么设置_Laravel Crontab调度器配置
绝密ChatGPT指令:手把手教你生成HR无法拒绝的求职信
,交易猫的商品怎么发布到网站上去?
Laravel模型事件有哪些_Laravel Model Event生命周期详解
详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点
Python正则表达式进阶教程_复杂匹配与分组替换解析
网页设计与网站制作内容,怎样注册网站?
laravel服务容器和依赖注入怎么理解_laravel服务容器与依赖注入解析
香港服务器选型指南:免备案配置与高效建站方案解析
悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤
Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】
如何快速查询网站的真实建站时间?
如何快速上传建站程序避免常见错误?
Android Socket接口实现即时通讯实例代码
Internet Explorer官网直接进入 IE浏览器在线体验版网址
Laravel如何实现本地化和多语言支持_Laravel多语言配置与翻译文件管理
Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】
如何使用 jQuery 正确渲染 Instagram 风格的标签列表
JavaScript如何实现错误处理_try...catch如何捕获异常?
Java解压缩zip - 解压缩多个文件或文件夹实例
html5audio标签播放结束怎么触发事件_onended回调方法【教程】
Android中AutoCompleteTextView自动提示
如何基于云服务器快速搭建个人网站?
JS去除重复并统计数量的实现方法
laravel怎么用DB facade执行原生SQL查询_laravel DB facade原生SQL执行方法
如何在 Python 中将列表项按字母顺序编号(a.、b.、c. …)
如何在Windows服务器上快速搭建网站?
如何在IIS中配置站点IP、端口及主机头?

