概述

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的更多相关文章

  1. JDK Collection 源码分析(2)—— List

    JDK List源码分析 List接口定义了有序集合(序列).在Collection的基础上,增加了可以通过下标索引访问,以及线性查找等功能. 整体类结构 1.AbstractList   该类作为L ...

  2. JDK AtomicInteger 源码分析

    @(JDK)[AtomicInteger] JDK AtomicInteger 源码分析 Unsafe 实例化 Unsafe在创建实例的时候,不能仅仅通过new Unsafe()或者Unsafe.ge ...

  3. 设计模式(十八)——观察者模式(JDK Observable源码分析)

    1 天气预报项目需求,具体要求如下: 1) 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方). 2) 需要设计开放型 API,便于其他第三方也能接入气象 ...

  4. JDK Collection 源码分析(3)—— Queue

    @(JDK)[Queue] JDK Queue Queue:队列接口,对于数据的存取,提供了两种方式,一种失败会抛出异常,另一种则返回null或者false.   抛出异常的接口:add,remove ...

  5. JDK Collection 源码分析(1)—— Collection

    JDK Collection   JDK Collection作为一个最顶层的接口(root interface),JDK并不提供该接口的直接实现,而是通过更加具体的子接口(sub interface ...

  6. 源码分析:CountDownLatch 之倒计时门栓

    简介 CountDownLatch 是JDK1.5 开始提供的一种同步辅助工具,它允许一个或多个线程一直等待,直到其他线程执行的操作完成为止.在初始化的时候给定 CountDownLatch 一个计数 ...

  7. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(1)

    概述 前文「JDK源码分析-Lock&Condition」简要分析了 Lock 接口,它在 JDK 中的实现类主要是 ReentrantLock (可译为“重入锁”).ReentrantLoc ...

  8. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(2)

    概述 前文「JDK源码分析-AbstractQueuedSynchronizer(1)」初步分析了 AQS,其中提到了 Node 节点的「独占模式」和「共享模式」,其实 AQS 也主要是围绕对这两种模 ...

  9. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(3)

    概述 前文「JDK源码分析-AbstractQueuedSynchronizer(2)」分析了 AQS 在独占模式下获取资源的流程,本文分析共享模式下的相关操作. 其实二者的操作大部分是类似的,理解了 ...

随机推荐

  1. 二进制mariadb多实例

    实验环境: centos7.6 :IP: 192.168.99.110 1.首先下载二进制的压缩包,解压到一个指定的目录/hx/下 [root@centos7 hx]#tar xf mariadb-1 ...

  2. Linux搭建DHCP服务器

    Linux搭建DHCP服务器   实验目标: 通过本实验掌握基于Linux的DHCP服务器搭建技能. 本实验包含内容为yum的认识与使用,磁盘挂载的概念与使用,DHCP原理及配置,systemctl服 ...

  3. 2018.10.20 2018NOIP冲刺之酒厂选址

    题目传送门 明显能够看出有一个建图求路程的优化 然而发现10000*10000爆空间QAQ 为了做一些初始化方面的优化 我们发现了一个叫做前缀和的东西 可以在环上查到两个之间的最短距离 同时还要做一些 ...

  4. 2018.10.1 2018NOIP冲刺之立体图

    2008NOIP普及组立体图 请自行百度(事实上放不下了) 图不是很清楚 下面有 [输入] 输入文件 drawing.in 第一行有用空格隔开的 2 个整数 m 和 n,表示有 m*n  个格子 (1 ...

  5. Hive入门(一)

    1 Hive中的数据定义 1.1 存储与创建 Hive会为每个数据库创建一个目录.数据库中的表以子目录的形式存储. 有一个例外是default中的表,因为这个库本身没有目录. 数据库的顶级目录是hiv ...

  6. POJ 1743:Musical Theme(后缀数组+二分)

    题目链接 题意 有N个音符的序列来表示一首乐曲,每个音符都是1到88范围内的整数,现在要找一个重复的主题."主题"是整个音符序列的一个子串,它需要满足如下条件: 长度至少为5个音符 ...

  7. Python之Pandas库学习(二):数据读写

    1. I/O API工具 读取函数 写入函数 read_csv to_csv read_excel to_excel read_hdf to_hdf read_sql to_sql read_json ...

  8. cookie 和 session 设置

    cookie: 保存在浏览器上的一组键值对, 是由服务器让浏览器进行设置的 下次浏览器访问的时候会携带cookie. request是客户端请求, response是服务端响应. 读取客户端的cook ...

  9. MySql突然连接不上,报Can't connect to MySQL server on 'localhost' (10061),并且没有mysqld时解决方案

    今天连接数据库时突然连接不上,前一天还是好好的.打开数据库就报 Can't connect to MySQL server on 'localhost' (10061) 一直也知道是MySQL服务没有 ...

  10. akka 集群分片

    akka 集群 Sharding分片 分片上下级结构 集群(多台节点机) —> 每台节点机(1个片区) —> 每个片区(多个分片) —> 每个分片(多个实体) 实体: 分片管理的 A ...