【JDK】JDK源码分析-CountDownLatch
概述
CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行。
简单起见,可以把它理解为一个倒数的计数器:初始值为线程数,每个线程结束时执行减 1 操作,当计数器减到 0 时等待的线程再继续执行。
代码分析
CountDownLatch 的类签名和主要方法如下:
public class CountDownLatch {}

常用方法为:await()、await(long, TimeUnit) 和 countDown。其中两个 await 都是让当前线程进入等待状态(获取资源失败);而 countDown 方法是将计数器减去 1,当计数器为 0 的时候,那些处于等待状态的线程会继续执行(获取资源成功)。
构造器代码如下:
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
构造器(该构造器是唯一的)传入一个正整数,且初始化了 sync 变量,Sync 是内部的一个嵌套类,继承自 AQS。
await / await(long, TimeUnit):
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
countDown:
public void countDown() {
sync.releaseShared(1);
}
其中,acquireSharedInterruptibly、tryAcquireSharedNanos 和 releaseShared 都是 AQS 中「共享模式」的方法,具体代码可参考前文「JDK源码分析-AbstractQueuedSynchronizer(3)」的分析。
嵌套类 Sync 代码如下:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 构造器,初始化 AQS 的 state 变量
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
// 尝试获取资源的操作
// 只有当 state 变量为 0 的时候才能获取成功(返回 1)
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 尝试释放资源的操作
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
// 该操作就是尝试把 state 变量减去 1
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
Sync 继承了 AQS 抽象类,根据 AQS 可知,acquireSharedInterruptibly 和 tryAcquireSharedNanos 方法的实现都调用了 tryAcquireShared。
流程说明:通常先把 CountDownLatch 的计数器(state)初始化为 N,执行 wait 操作就是尝试以共享模式获取资源,而每次 countDown 操作就是将 N 减去 1,只有当 N 减到 0 的时候,才能获取成功(tryAcquireShared 方法),然后继续执行。
场景举例
为便于理解该类的用法,举两个简单的例子来说明它的使用场景。
场景 1:一个线程等待多个线程执行完之后再继续执行
public void test() throws InterruptedException {
int count = 5;
// CountDownLatch 的初始化计数器为 5
// 注意线程数和计数器保持一致
CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
int finalI = i;
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is working ..");
// 每个线程执行结束时执行 countDown
countDownLatch.countDown();
}).start();
}
// 主线程进入等待状态(尝试获取资源,成功后才能继续执行)
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + " go on ..");
}
/* 输出结果:
Thread-0 is working ..
Thread-1 is working ..
Thread-2 is working ..
Thread-3 is working ..
Thread-4 is working ..
main go on ..
*/
场景 2:一个线程到达指定条件后,通知另一个线程
private static volatile List<Integer> list = new ArrayList<>();
private static void test() {
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(() -> {
if (list.size() != 5) {
try {
// list 的大小为 5 时再继续执行,否则等待
// 等待 state 减到 0
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " start..");
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
list.add(i);
System.out.println(Thread.currentThread().getName() + " add " + i);
if (list.size() == 5) {
// 满足条件时将 state 减 1
countDownLatch.countDown();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/* 输出结果:
Thread-1 add 0
Thread-1 add 1
Thread-1 add 2
Thread-1 add 3
Thread-1 add 4
Thread-0 start..
Thread-1 add 5
Thread-1 add 6
Thread-1 add 7
Thread-1 add 8
Thread-1 add 9
*/
小结
CountDownLatch 可以理解为一个倒数的计数器,它的典型应用场景就是一个线程等待几个线程执行结束后再继续执行。其内部是基于 AQS 的共享模式实现的。
相关阅读:
JDK源码分析-AbstractQueuedSynchronizer(3)
Stay hungry, stay foolish.

PS: 本文首发于微信公众号【WriteOnRead】。
【JDK】JDK源码分析-CountDownLatch的更多相关文章
- JDK Collection 源码分析(2)—— List
JDK List源码分析 List接口定义了有序集合(序列).在Collection的基础上,增加了可以通过下标索引访问,以及线性查找等功能. 整体类结构 1.AbstractList 该类作为L ...
- JDK AtomicInteger 源码分析
@(JDK)[AtomicInteger] JDK AtomicInteger 源码分析 Unsafe 实例化 Unsafe在创建实例的时候,不能仅仅通过new Unsafe()或者Unsafe.ge ...
- 设计模式(十八)——观察者模式(JDK Observable源码分析)
1 天气预报项目需求,具体要求如下: 1) 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方). 2) 需要设计开放型 API,便于其他第三方也能接入气象 ...
- JDK Collection 源码分析(3)—— Queue
@(JDK)[Queue] JDK Queue Queue:队列接口,对于数据的存取,提供了两种方式,一种失败会抛出异常,另一种则返回null或者false. 抛出异常的接口:add,remove ...
- JDK Collection 源码分析(1)—— Collection
JDK Collection JDK Collection作为一个最顶层的接口(root interface),JDK并不提供该接口的直接实现,而是通过更加具体的子接口(sub interface ...
- 源码分析:CountDownLatch 之倒计时门栓
简介 CountDownLatch 是JDK1.5 开始提供的一种同步辅助工具,它允许一个或多个线程一直等待,直到其他线程执行的操作完成为止.在初始化的时候给定 CountDownLatch 一个计数 ...
- 【JDK】JDK源码分析-AbstractQueuedSynchronizer(1)
概述 前文「JDK源码分析-Lock&Condition」简要分析了 Lock 接口,它在 JDK 中的实现类主要是 ReentrantLock (可译为“重入锁”).ReentrantLoc ...
- 【JDK】JDK源码分析-AbstractQueuedSynchronizer(2)
概述 前文「JDK源码分析-AbstractQueuedSynchronizer(1)」初步分析了 AQS,其中提到了 Node 节点的「独占模式」和「共享模式」,其实 AQS 也主要是围绕对这两种模 ...
- 【JDK】JDK源码分析-AbstractQueuedSynchronizer(3)
概述 前文「JDK源码分析-AbstractQueuedSynchronizer(2)」分析了 AQS 在独占模式下获取资源的流程,本文分析共享模式下的相关操作. 其实二者的操作大部分是类似的,理解了 ...
随机推荐
- JavaScript 常见的六种继承方式
JavaScript 常见的六种继承方式 前言 面向对象编程很重要的一个方面,就是对象的继承.A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 大部分 ...
- iOS 矢量图pdf替换2倍3倍图
一.在开发中矢量图的使用大大提高设计的切图效率! ios 中可以用pdf替换现在的2倍3倍图(1倍图已经忽略) 1.将pdf矢量图拖进工程Assets.xcassets中 2.按照步骤操作 3.矢量图 ...
- 控制台程序秒变Windows服务(Topshelf)
项目中有些时候需要写服务,一般我们都是先创建控制台程序,测试,运行,成功之后再创建windows服务程序,这样好麻烦啊,有没有简单的控制台程序直接变成Widnows服务,经过查找,找到了Topshel ...
- 动态代理模拟实现aop
AOP实现起来代码相当简单.主要核心是动态代理和反射. 一.接口类: public interface MethodDao { public void sayHello(); } 二.接口实现类: p ...
- Java编程思想:Preferences
import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; public class Test ...
- Java多线程下载器FileDownloader(支持断点续传、代理等功能)
前言 在我的任务清单中,很早就有了一个文件下载器,但一直忙着没空去写.最近刚好放假,便抽了些时间完成了下文中的这个下载器. 介绍 同样的,还是先上效果图吧. Jar包地址位于 FileDownload ...
- NOIP2018普及T2暨洛谷P5016 龙虎斗
题目链接:https://www.luogu.org/problemnew/show/P5016 分析: 这是一道模拟题.看到题目,我们首先要把它细致的读明白,模拟题特别考察细节,往往会有想不到的坑点 ...
- Excel催化剂开源第36波-图片Exif信息提取,速度超快,信息超全
Excel催化剂在文件处理方面,功能做到极致,但其实很大功劳都是引用一些开源社区的轮子库,不敢独占好处,此篇给大家分享下抓取图片的Exif信息的好用的轮子. 此篇对应的Excel催化剂功能实现:第83 ...
- JavaScript知识点---->运算规则与运算(逻辑、位)
*在js中不同类型之间的运算,所得到结果的类型也会有所变化: string + number = string string + boolean = string string + undefiend ...
- 代码中批量执行Oracle SQL语句
今天在写一个工具(winform),作用是批量的INSERT OR UPDATE ORACLE数据库中的一个表. 执行的时候老是报错“[911] ORA-00911: invalid charact ...