关注王有志,一个分享硬核Java技术的互金摸鱼侠

欢迎你加入Java人的提桶跑路群共同富裕的Java人

今天我们来聊一聊AQS家族中的另一个重要成员CountDownLatch。关于CountDownLatch的面试题并不多,除了问“是什么”和“如何实现的“外,CountDownLatch还会和CyclicBarrier进行对比:

  • 什么是CountDownLatch?它是如何实现的?

  • CountDownLatch和CyclicBarrier有什么区别?

按照惯例,我们依旧是按照“是什么”,“怎么用”和“如何实现的”这3步来分析CountDownLatch,至于与CyclicBarrier的差异,下一篇我们再详细分析。

Tips:今天的“是什么”和“怎么用”合并了。

CountDownLatch的使用

不知道你有没有参加过那种感动老板,并伴以“提升”组织凝聚力为主旨的公司团建?通常行政会组织一场越野徒步活动,规定每个人都到达终点后才能吃饭,美名其曰“不抛弃不放弃的团队精神”。而老板会早早的在终点拿着花名册等待,当员工到达终点后,在花名册上划掉自己的名字,当最后一名员工到达终点后,还要敲响锣鼓,告知老板可以开始下一轮的折磨了。

那么这样一场越野徒步活动就可以用CountDownLatch来进行简单的代码描述:

CountDownLatch countDownLatch = new CountDownLatch(10);

// 10个人进行越野徒步
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
try {
// 每个人比前一个选手晚1秒
TimeUnit.SECONDS.sleep((finalI + 1));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("选手[" + finalI + "]到达终点!!!");
countDownLatch.countDown();
}).start();
} // 老板在目的地吃瓜,等待每个选手到达
countDownLatch.await();
// 开饭啦!
System.out.println("老板说:所有人都到齐了,午饭是每人一个吐司!!!");

看到这里,参加过此类团建活动的小伙伴是不是血压有些高了?但是你先别高,因为在这样一场血压飙升的团建中,我们已经不知不觉的掌握了CountDownLatch的用法了。

我们先试着从名字来理解CountDownLatch,CountDownLatch是一个组合词,CountDown译为“倒计时”,Latch译为“门闩”,结合起来就是倒计时结束后打开门闩(进行后续的动作)。再来看Doug Lea是如何解释CountDownLatch的作用的:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

CountDownLatch是一个同步辅助工具,它允许一个或多个线程等待其他线程完成操作(进而执行后续操作)。

需要注意的是,CountDownLatch允许一个或多个线程进入等待,我们只需要在不同的线程中调用CountDownLatch.await就可以实现多个线程的等待。

CountDownLatch的原理

先来看作为AQS家族的成员,CountDownLatch是如何与AQS产生联系的:

很熟悉的结构,与ReentrantLock和Semaphore一样,都是内部的同步器类Sync继承了AQS,但不同的是CountDownLatch中的Sync不再是抽象类。

既然是继承自AQS,并且内部有计数器(倒计数也是计数)的使用,那么我们就再次搬出《AQS的今生,构建出JUC的基础》中那段关于同步状态作为计数器特性的说明:

AQS中,state不仅用作表示同步状态,也是某些同步器实现的计数器,如:Semaphore中允许通过的线程数量,ReentrantLock中可重入特性的实现,都依赖于state作为计数器的特性。

虽然没有举CountDownLatch的例子,但我知道在经过Semaphore的分析后你一定能够猜到CountDownLatch是如何使用同步状态作为计数器特性的。接下来我们就一起来看一下同步状态在CountDownLatch中的应用。

构造方法

通过AQS家族成员的类图可以看到,CountDownLatch中的同步器Sync并没有公平与非公平的区别,因此构造器只需要提供设置计数的能力即可:

public class CountDownLatch {
public CountDownLatch(int count) {
if (count < 0){
throw new IllegalArgumentException("count < 0");
}
this.sync = new Sync(count);
} private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
}
} public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
protected final void setState(int newState) {
state = newState;
}
}

不出所料,CountDownLatch的计数依旧是回归到了AQS的state上。

countDown方法

回到徒步活动中,员工到达终点后,需要在花名册上划掉自己的名字,最后一名到达后还要敲响锣鼓。在代码实现中,我们使用了CountDownLatch.countDown表示员工到达的状态,并执行相应的动作:

public class CountDownLatch {
public void countDown() {
sync.releaseShared(1);
} private static final class Sync extends AbstractQueuedSynchronizer {
protected boolean tryReleaseShared(int releases) {
for (;;) {
// 获取同步状态
int c = getState();
// 同步状态为0,返回失败
if (c == 0){
return false;
}
// 计数减1,并通过CAS更新
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
// 计数器为0时返回true
return nextc == 0;
}
}
}
}
} public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
}

回忆下《详解AQS家族的成员:Semaphore》中Semaphore#release方法的实现,是不是觉得似曾相识?同样是执行Sync#tryReleaseShared方法,并在成功后调用AQS的doReleaseShared方法。区别是Semaphore#tryReleaseShared的实现是计数加1,而CountDownLatch#tryReleaseShared实现是计数减1。

我们注意另一个问题,CountDownLatch的Sync#tryReleaseShared方法只有在计数器减为0时才会返回true,此时能进入AQS的doReleaseShared方法,否则都只是执行了计数器减一的操作。

此外,我们也知道AQS的doReleaseShared方法起到了唤醒AQS等待队列中节点的作用,也就是说只有在计数器减为0时,CountDownLatch才会执行一次唤醒工作

Tips:AQS的doReleaseShared已经在《详解AQS家族的成员:Semaphore》中分析过了,就不再赘述了~~

await方法

我们知道老板一早就乘车到达了终点等待,那么老板是如何判断自己要等待呢?老板提前抵达终点后,拿出花名册统计到达人数,当发现还有人没有到达终点时,他就准备打个盹,睡一觉。

我们使用了CountDownLatch.await表示老板进入等待状态:

public class CountDownLatch {
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
} public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (tryAcquireShared(arg) < 0) {
doAcquireSharedInterruptibly(arg);
}
}
}

是不是还是很眼熟?与Semaphore一样使用了AQS的acquireSharedInterruptibly方法,那我们重点关注CountDownLatch的Sync#tryAcquireShared方法:

public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
protected int tryAcquireShared(int acquires) {
// 同步状态为0返回1,不为0返回-1
return (getState() == 0) ? 1 : -1;
}
}
}

该方法对同步状态做出了判断,结合AQS的acquireSharedInterruptibly方法我们可以得到以下结论:

  • 当同步状态等于0时,tryAcquireShared返回1,不执行doAcquireSharedInterruptibly,即执行了足够次数的countDownLatch#countDown,无需进入等待队列;

  • 当同步状态不等于0时,tryAcquireShared返回-1,执行doAcquireSharedInterruptibly,即尚未执行足够次数的countDownLatch#countDown,需要进入等待队列。

简单来说就是在调用CountDownLatch#await方法时计数器不为0构建等待队列,为0就什么也不执行。

Tips:AQS的doAcquireSharedInterruptibly已经在《详解AQS家族的成员:Semaphore》中分析过了,就不再赘述了~~

结语

关于CountDownLatch的内容到这里就结束了,内容并不多。当我们不熟悉AQS的时候,不认识CountDownLatch的时候,会觉得CountDownLatch是一种“挺高级”的工具,但当我们深入其中时就会发现,“高级”的技术其实并不难学。

好了,如果本文对你有帮助的话,还希望你不要吝啬点赞。最后欢迎大家关注分享硬核技术的金融摸鱼侠王有志,以及关注我的专栏《Java面试都问啥?》,我们下次再见!

19.详解AQS家族的成员:CountDownLatch的更多相关文章

  1. 详解AQS的7个同步组件

    摘要:AQS的全称为Abstract Queued Synchronizer,是在J.U.C(java.util.concurrent)下子包中的类. 本文分享自华为云社区<[高并发]AQS案例 ...

  2. Java基础进阶:多态与接口重点摘要,类和接口,接口特点,接口详解,多态详解,多态中的成员访问特点,多态的好处和弊端,多态的转型,多态存在的问题,附重难点,代码实现源码,课堂笔记,课后扩展及答案

    多态与接口重点摘要 接口特点: 接口用interface修饰 interface 接口名{} 类实现接口用implements表示 class 类名 implements接口名{} 接口不能实例化,可 ...

  3. 并发编程——详解 AQS CLH 锁

    从 acquire 方法开始 -- 获取 为什么 AQS 需要一个虚拟 head 节点 reelase 方法如何释放锁 总结 前言 AQS 是 JUC 中的核心,其中封装了资源的获取和释放,在我们之前 ...

  4. 从ReentrantLock详解AQS原理源码解析

    数据结构 java.util.concurrent.locks.AbstractQueuedSynchronizer类中存在如下数据结构. // 链表结点 static final class Nod ...

  5. 详解AQS中的condition源码原理

    摘要:condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁. 本文分享自华为云社区<AQS中的condition源码原理详细分析>,作者:breakDawn. ...

  6. 转】Mahout推荐算法API详解

    原博文出自于: http://blog.fens.me/mahout-recommendation-api/ 感谢! Posted: Oct 21, 2013 Tags: itemCFknnMahou ...

  7. [转]Mahout推荐算法API详解

    Mahout推荐算法API详解 Hadoop家族系列文章,主要介绍Hadoop家族产品,常用的项目包括Hadoop, Hive, Pig, HBase, Sqoop, Mahout, Zookeepe ...

  8. ReentrantLock源码详解

    前言 以前只知道ReentrantLock底层基于AQS实现,相对于(旧版本的)synchronized: 更轻量(基于CAS而不是管程),由JDK实现 可以实现公平/非公平 可中断等待 可绑定多个条 ...

  9. Java并发之AQS详解(转)

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  10. Java AQS详解(转)

    原文地址 一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同 ...

随机推荐

  1. matplotlab可视化学习

    1 使用pip安装 使用 Python 包管理器 pip 来安装 Matplotlib 是一种最轻量级的方式.打开 CMD 命令提示符窗口,并输入以下命令: pip install matplotli ...

  2. day02-Redis命令

    Redis命令 1.Redis数据结构介绍 Redis是一个key-value的数据库,key一般是String类型,value的类型多种多样,value常见的八种类型: Redis支持五种基本的数据 ...

  3. Kubernetes入门实践(ConfigMap/Secret)

    Kubernetes中用于管理配置信息的两种对象: ConfigMap和Secret,可使用它们来灵活地配置和定制应用.应用程序有很多类别的配置信息,从数据安全的角度看可分为明文配置和机密配置,明文配 ...

  4. vue-element-admin 动态菜单改造

    vue-element-admin 动态菜单改造 vue-element-admin 是一款优秀后台前端解决方案,它基于 vue 和 element-ui实现.开源后台管理系统解决方案项目 Boot- ...

  5. GraalVM(云原生时代的Java)和IoT在边缘侧落地与实践

    环顾四周,皆是对手! 云时代的掉队者,由于Java启动的高延时.对资源的高占用.导致在Serverless及FaaS架构下力不从心,在越来越流行的边缘计算.IoT方向上也是难觅踪影; Java语言在业 ...

  6. 注解:@RequiredArgsConstructor、 @Validated、 @Valid、 @Lazy

    1. lombok注解:@RequiredArgsConstructor Spring 依赖注入方式 1.通过 @Autowire.@Resource 等注解注入, 2.通过构造器的方式进行依赖注入. ...

  7. Python_13 接口测试openpyxl和表操作

    一.openpyxl 安装   pip install openpyxl  在Terminal中输入 excel操作步骤 找到目标excel 打开 读取数据.编辑excel单元格 保存 关闭 open ...

  8. 简单实用Ecplise常用快捷键

    简单实用Eclipse常用快捷键 用了Eclipse两年了,简单总结下目前我经常使用的快捷键!!! 1. Ctrl+Shift+R 功能:打开资源,这组快捷键可以让你打开你的工程中的任何一个文件 操作 ...

  9. Istio数据面新模式:Ambient Mesh技术解析

    摘要:Ambient Mesh以一种更符合大规模落地要求的形态出现,克服了大多数Sidecar模式的固有缺陷,让用户无需再感知网格相关组件,真正将网格下沉为基础设施. 本文分享自华为云社区<华为 ...

  10. 2023-03-30:用Go语言改写FFmpeg示例decode_audio.c,实现高效音频解码。

    2023-03-30:用Go语言改写FFmpeg示例decode_audio.c,实现高效音频解码. 答案2023-03-30: 这个程序的主要功能是将 MP2 音频文件解码为 PCM 格式,并输出到 ...