概述

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. 总结关于CPU的一些基本知识

    关于CPU和程序的执行 CPU是计算机的大脑. 程序的运行过程,实际上是程序涉及到的.未涉及到的一大堆的指令的执行过程. 当程序要执行的部分被装载到内存后,CPU要从内存中取出指令,然后指令解码(以便 ...

  2. 高性能微服务网关.NETCore客户端Kong.Net开源发布

    前言 项目地址:https://github.com/lianggx/Kong.Net 你的支持使我们更加强大,请单击 star 让更多的 .NETCore 认识它. 拥抱开源的脚步,我们从来都是一直 ...

  3. 试题--创建三个进程/线程,依次输出 A、B、C

    这是一道机试题,大概的预期执行结果如下图所示 最近刚好在学习linux编程,便使用多线程及多进程分别实现了一遍,其中多线程较为简单,使用0/1信号量在线程间实现生产者/消费者即可:多进程则稍微复杂一些 ...

  4. Ural 2072:Kirill the Gardener 3(DP)

    http://acm.timus.ru/problem.aspx?space=1&num=2072 题意:有n朵花,每朵花有一个饥渴值.现在浇花,优先浇饥渴值小的(即从小到大浇),浇花需要耗费 ...

  5. bzoj1052 9.20考试 第二题 覆盖问题

    1052: [HAOI2007]覆盖问题 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2004  Solved: 937[Submit][Statu ...

  6. 【最小生成树之Kruskal例题-建设电力系统】-C++

    前置知识点Kruskal最短路算法,如果没掌握的请先去掌握! 描述 小明所在的城市由于下暴雪的原因,电力系统严重受损.许多电力线路被破坏,因此许多村庄与主电网失去了联系.政府想尽快重建电力系统,所以, ...

  7. [Spring-Cloud-Alibaba] Sentinel 整合RestTemplate & Feign

    Sentinel API Github : WIKI Sphu (指明要保护的资源名称) Tracer (指明调用来源,异常统计接口) ContextUtil(标示进入调用链入口) 流控规则(针对来源 ...

  8. java学习笔记(基础篇)—面向对象编程之封装、继承、多态

    一. OOP中的基本概念 Java的编程语言是面向对象的,采用这种语言进行编程称为面向对象编程(Object-Oriented Programming, OOP), 它允许设计者将面向对象设计实现为一 ...

  9. 小记---idea springboot 报错没有get或者set方法

    给idea 安装一个插件即可

  10. C#3.0新增功能10 表达式树 02 说明

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构.表达式树和 Roslyn API 中用于生成分析器和 Cod ...