写在开头

在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器),而CountDownLatch又是其中典型的代表,我们今天就继续来学一下这个同步工具类!

CountDownLatch有何作用?

我们知道AQS是专属于构造锁和同步器的一个抽象工具类,基于它Java构造出了大量的常用同步工具,如ReentrantLock、Semaphore、ReentrantReadWriteLock、SynchronousQueue等等,我们今天的主角CountDownLatch同样如此。

CountDownLatch(倒时器)允许N个线程阻塞在同一个地方,直至所有线程的任务都执行完毕。CountDownLatch 有一个计数器,可以通过countDown()方法对计数器的数目进行减一操作,也可以通过await()方法来阻塞当前线程,直到计数器的值为 0。

CountDownLatch的底层原理

想要迅速了解一个Java类的内部构造,或者使用原理,最快速直接的办法就是看它的源码,这是很多初学者比较抵触的,会觉得很多封装起来的源码都晦涩难懂,诚然很多类内部实现是复杂,但我们作为Java工程师也不能只追求CRUD呀,培养自己看源码的习惯,硬着头皮看段时间,代码能力绝对会提升的!

废话说的有点多了,我们直接进入CountDownLatch内部去看看它的底层原理吧

【源码解析1】

//几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
}
private final Sync sync;
//构造方法中初始化count值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS,并实现其提供的钩子方法,通过封装AQS中的state为count来确定多个线程的计时器。

countDown()方法

【源码解析2】

//核心方法,内部封装了共享模式下的线程释放
public void countDown() {
//内部类Sync,继承了AQS
sync.releaseShared(1);
}
//AQS内部的实现
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//唤醒后继节点
doReleaseShared();
return true;
}
return false;
}

在CountDownLatch中通过countDown来减少倒计时数,这是最重要的一个方法,我们继续跟进源码看到它通过releaseShared()方法去释放锁,这个方法是AQS内部的默认实现方法,而在这个方法中再一次的调用了tryReleaseShared(arg),这是一个AQS的钩子方法,方法内部仅有默认的异常处理,真正的实现由CountDownLatch内部类Sync完成

【源码解析3】

// 对 state 进行递减,直到 state 变成 0;
// 只有 count 递减到 0 时,countDown 才会返回 true
protected boolean tryReleaseShared(int releases) {
// 自选检查 state 是否为 0
for (;;) {
int c = getState();
// 如果 state 已经是 0 了,直接返回 false
if (c == 0)
return false;
// 对 state 进行递减
int nextc = c-1;
// CAS 操作更新 state 的值
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

await()方法

除了countDown()方法外,在CountDownLatch中还有一个重要方法就是 await ,在多线程环境下,线程的执行顺序并不一致,因此,对于一个倒时器也说,先开始的线程应该阻塞等待直至最后一个线程执行完成,而实现这一效果的就是await()方法!

【源码解析4】

// 等待(也可以叫做加锁)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 带有超时时间的等待
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

其中await()方法可以配置带有时间参数的,表示最大阻塞时间,当调用 await() 的时候,如果 state 不为 0,那就证明任务还没有执行完毕,await() 就会一直阻塞,也就是说 await() 之后的语句不会被执行。然后,CountDownLatch 会自旋 CAS 判断 state是否等于0,若是就会释放所有等待的线程,await() 方法之后的语句得到执行。

CountDownLatch的使用

由于await的实现步骤和countDown类似,我们就不贴源码了,大家自己跟进去也很容易看明白,我们现在直接来一个小demo感受一下如何使用CountDownLatch做一个倒时器

【代码样例1】

public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建一个倒计数为 3 的 CountDownLatch
CountDownLatch latch = new CountDownLatch(3); Thread service1 = new Thread(new Service("3", 1000, latch));
Thread service2 = new Thread(new Service("2", 2000, latch));
Thread service3 = new Thread(new Service("1", 3000, latch)); service1.start();
service2.start();
service3.start(); // 等待所有服务初始化完成
latch.await();
System.out.println("发射");
} static class Service implements Runnable {
private final String name;
private final int timeToStart;
private final CountDownLatch latch; public Service(String name, int timeToStart, CountDownLatch latch) {
this.name = name;
this.timeToStart = timeToStart;
this.latch = latch;
} @Override
public void run() {
try {
Thread.sleep(timeToStart);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name);
// 减少倒计数
latch.countDown();
}
}
}

输出:

3
2
1
发射

执行结果体现出了倒计时的效果每隔1秒进行3,2,1的倒数;其实除了倒计时器外CountDownLatch还有另外一个使用场景:实现多个线程开始执行任务的最大并行性

多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。

具体做法是: 初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 (new CountDownLatch(1)),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。

【代码样例2】

public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
System.out.println("5位运动员就位!");
//等待发令枪响
countDownLatch.await(); System.out.println(Thread.currentThread().getName() + "起跑!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 裁判准备发令
Thread.sleep(2000);
//发令枪响
countDownLatch.countDown();
}
}

输出:

5位运动员就位!
5位运动员就位!
5位运动员就位!
5位运动员就位!
5位运动员就位!
Thread-0起跑!
Thread-3起跑!
Thread-4起跑!
Thread-1起跑!
Thread-2起跑!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这的更多相关文章

  1. 想入职阿里的Java开发者必看,阿里巴巴面试官实战经验分享!

    最近社区Java技术进阶群的小伙伴总是会问,如何面试阿里Java技术岗,需要什么条件,做哪些准备:小编就这些问题找到了阿里技术团队中在一线真正带Java开发团队并直接参与技术面试的专家,分享了自身在筛 ...

  2. 面试官:怎么做JDK8的垃圾收集器的调优?

    面试官:怎么做JDK8的垃圾收集器的调优? 看着面试官真诚的眼神,心中暗想看起来年纪轻轻却提出如此直击灵魂的问题.擦了擦额头上汗,我稍微调整了一下紧张的情绪,对面试官说: 在JDK8中有Serial收 ...

  3. 面试官:线程池如何按照core、max、queue的执行循序去执行?(内附详细解析)

    前言 这是一个真实的面试题. 前几天一个朋友在群里分享了他刚刚面试候选者时问的问题:"线程池如何按照core.max.queue的执行循序去执行?". 我们都知道线程池中代码执行顺 ...

  4. 【对线面试官】CountDownLatch和CyclicBarrier的区别

    <对线面试官>系列目前已经连载31篇啦,这是一个讲人话面试系列 [对线面试官]Java注解 [对线面试官]Java泛型 [对线面试官] Java NIO [对线面试官]Java反射 &am ...

  5. 8年经验面试官详解 Java 面试秘诀

      作者 | 胡书敏 责编 | 刘静 出品 | CSDN(ID:CSDNnews) 本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三 ...

  6. 「干货」面试官问我如何快速搜索10万个矩形?——我说RBush

    「干货」面试官问我如何快速搜索10万个矩形?--我说RBUSH 前言 亲爱的coder们,我又来了,一个喜欢图形的程序员‍,前几篇文章一直都在教大家怎么画地图.画折线图.画烟花,难道图形就是这样嘛,当 ...

  7. 以技术面试官的经验分享毕业生和初级程序员通过面试的技巧(Java后端方向)

    本来想分享毕业生和初级程序员如何进大公司的经验,但后来一想,人各有志,有程序员或许想进成长型或创业型公司或其它类型的公司,所以就干脆来分享些提升技能和通过面试的技巧,技巧我讲,公司你选,两厢便利. 毕 ...

  8. BAT面试官告诉你如何回答你的职业规划

    前言(Why) 在面试中不论是在一面二面三面这种技术面,还是在最后的hr面,经常会被人问及,"谈谈你的职业规划"这种问题,我们回答的很可能会给我们的面试表现加分,如果回答地不好,对 ...

  9. 面试官:你连RESTful都不知道我怎么敢要你?

    目录 01 前言 02 RESTful的来源 03 RESTful6大原则 1. C-S架构 2. 无状态 3.统一的接口 4.一致的数据格式 4.系统分层 5.可缓存 6.按需编码.可定制代码(可选 ...

  10. 面试官:你看过Redis数据结构底层实现吗?

    面试中,redis也是很受面试官亲睐的一部分.我向在这里讲的是redis的底层数据结构,而不是你理解的五大数据结构.你有没有想过redis底层是怎样的数据结构呢,他们和我们java中的HashMap. ...

随机推荐

  1. xml中xsd、xsi、xmlns的含义

    XML是可扩展标记语言,它定义了按格式编码文件的一系列规则[3],编码的文件是机器可读和人可读的.XML文件对于机器可读是基于XSD(XML Schema Definition)[1]的.XSD是受W ...

  2. 二: sql模式(sql_mode)

    # sql_mode 1 介绍 sql_mode 会影响 MySQL支持的SQL语法以及它执行的数据验证检查.通过设置sql_mode,可以完成不同严格程度 的数据校验,有效地保障数据准确性. MyS ...

  3. Java 多态 解释+案例

    1 package com.bytezreo.duotai; 2 /** 3 * 4 * @Description 面向对象的特征三 ------多态性 5 * @author Bytezero·zh ...

  4. (广州南沙)vue知识点整理2021,主要是防止忘记防备快速翻看

         ///////////////////////////   vue 中使用路由技巧:router //////////////////////////////// 写笔记说明,之前在江门工作 ...

  5. [VueJsDev] 基础知识 - 常见编码集

    [VueJsDev] 目录列表 https://www.cnblogs.com/pengchenggang/p/17037320.html 常用编码集 ::: details 目录 目录 常用编码集 ...

  6. echart 带表格

    "echarts": "^5.2.2", ChartSituation1 <template> <div> <EChartTemp ...

  7. STM32芯片SPI接口接收数据左移一位问题定位总结

    一 问题 最近在一个项目中,调试SPI的过程中遇到一个问题--接收数据整体向左移了一位(1bit).SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节( ...

  8. opus编解码的特色和优点

    概念原理   Opus是一个有损音频压缩的数字音频编码格式,由Xiph.Org基金会开发,之后由互联网工程任务组(IETF)进行标准化,目标是希望用单一格式包含声音和语音,取代Speex和Vorbis ...

  9. python中往json中添加文件的方法

    一 前言: python中常用的一种方式,这里给大家列出来一下. 二 实例 比如,最简单的一个json文件 test_json = { "a": 1, "b": ...

  10. AirPlay、DLNA、Miracast三大无线应用协议科普

    作为经常玩wifi的,wifi的应用层协议就要好好分析一下,做一些特殊的应用,还是非常有必要的.这里,就给学习一下wifi的三大无线传输技术. AirPlayAirPlay 是苹果开发的一种无线技术, ...