面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这...

2024-04-14 23:20

本文主要是介绍面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在开头

在很多的面经中都看到过提问 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,继承了AQSsync.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 是否为 0for (;;) {int c = getState();// 如果 state 已经是 0 了,直接返回 falseif (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 的 CountDownLatchCountDownLatch 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;}@Overridepublic 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吗?详细说一说,我:啊这...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/904305

相关文章

MySql match against工具详细用法

《MySqlmatchagainst工具详细用法》在MySQL中,MATCH……AGAINST是全文索引(Full-Textindex)的查询语法,它允许你对文本进行高效的全文搜素,支持自然语言搜... 目录一、全文索引的基本概念二、创建全文索引三、自然语言搜索四、布尔搜索五、相关性排序六、全文索引的限制七

python中各种常见文件的读写操作与类型转换详细指南

《python中各种常见文件的读写操作与类型转换详细指南》这篇文章主要为大家详细介绍了python中各种常见文件(txt,xls,csv,sql,二进制文件)的读写操作与类型转换,感兴趣的小伙伴可以跟... 目录1.文件txt读写标准用法1.1写入文件1.2读取文件2. 二进制文件读取3. 大文件读取3.1

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

如何在Mac上安装并配置JDK环境变量详细步骤

《如何在Mac上安装并配置JDK环境变量详细步骤》:本文主要介绍如何在Mac上安装并配置JDK环境变量详细步骤,包括下载JDK、安装JDK、配置环境变量、验证JDK配置以及可选地设置PowerSh... 目录步骤 1:下载JDK步骤 2:安装JDK步骤 3:配置环境变量1. 编辑~/.zshrc(对于zsh

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优