CountDownLatch倒计时器源码解读与使用

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

目录

1. 前言

2. CountDownLatch有什么用

3. CountDownLatch底层原理

3.1. countDown()方法

3.2. await()方法

4. CountDownLatch的基本使用

5. 总结


1. 前言

在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器)、CAS操作等知识,而 CountDownLatch 又是JUC包下一个比较常见的同步工具类,我们今天就继续来学一下这个同步工具类!

2. CountDownLatch有什么用

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

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

这个就很类似我们的Moba类游戏的游戏加载过程,所有玩家几乎是一起进入游戏,加载快的玩家要等待加载慢的玩家,只有当全部玩家加载完成才能进入游戏,而CountDownLatch就类似于这个过程中的发令枪。

3. CountDownLatch底层原理

想要迅速了解一个Java类的内部构造,或者使用原理,最快速直接的办法就是看它的源码,这是很多初学者比较抵触的,会觉得很多封装起来的源码都晦涩难懂,诚然很多类内部实现是复杂,我也是慢慢从刚开始阅读Mybatis源码,到后来阅读JDK多线程相关的源码,尝试培养自己看源码的习惯,硬着头皮看段时间还是有不少收获的。

好的,我们直接进入CountDownLatch内部去看看它的底层原理吧

//几乎所有基于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);
}

是不是很熟悉?对又是Sync,与 Semaphore信号量一样,几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS

3.1. countDown()方法

//核心方法,内部封装了共享模式下的线程释放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完成,如下

// 对 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;}
}

当这个tryReleaseShared函数返回true时,也就是state扣减到了零,就会调用doReleaseShared唤醒CLH队列中阻塞等待的线程

3.2. await()方法

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

// 等待
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() 的时候,我们会调用aqs的一个模板方法acquireSharedInterruptibly(arg),如下:

public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

 Thread.interrupted()为判断线程书否为中断状态,如果为中断状态,抛出中断异常,否则会调用tryAcquireShared(arg)方法,tryAcquireShared方法为AQS的钩子函数,由静态内部类Snyc实现,如下

protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}

也就是说,当前state = 0就返回1,也就不会执行doAcquireSharedInterruptibly(arg),直接放行没有堵塞。否则会执行doAcquireSharedInterruptibly(arg)这个方法,这个方法内部主要是将当前线程加入CLH队列阻塞等待。

4. CountDownLatch的基本使用

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

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,多个线程同时被唤醒。

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起跑!

5. 总结

CountDownLatch 是一个多线程同步辅助类,它允许一个或多个线程等待一系列操作在其他线程中完成。这个机制类似于一场赛跑,选手们在起跑线准备,等待发令枪响后才能开始比赛。在 CountDownLatch 的场景中,线程们等待一个共同的信号,只有当计数器降至零时,它们才能继续执行。

CountDownLatch 提供了两个主要方法:countDown() 和 await()。countDown()方法用于将计数器减一,而 await() 方法会阻塞调用线程,直到计数器达到零。这种机制确保了所有线程都会等待必要的操作完成。

内部实现上,CountDownLatch 通过一个静态内部类 Sync 继承自 AbstractQueuedSynchronizer(AQS)。AQS 提供了一个框架,用于构建自定义的同步器。在 CountDownLatch 中,Sync 类通过重写 AQS 的钩子方法 tryReleaseShared() 和 tryAcquireShared() 来实现其同步机制。

  • tryReleaseShared() 方法用于在共享模式下尝试释放资源
  • tryAcquireShared() 方法用于在共享模式下尝试获取资源

我们可以发现,几乎所有基于AQS构造的同步类,实现原理都是差不多的,都是通过维护AQS中被volatile修饰的state变量作为竞态条件来实现线程同步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/640893.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何查看redisson-spring-boot-starter和SpringBoot 对应版本

如何查看redisson-spring-boot-starter和SpringBoot 对应版本 我目前没有找到官网的地址来来查看对应关系。 所以我只能找pom.xml来查看 先在mvnrepository 找到redisson-spring-boot-starter的列表 具体地址是&#xff1a;https://mvnrepository.com/artifact/org.redisso…

Oracle故障处理:ORA-00600错误处理思路

提前说明&#xff1a; 该故障&#xff0c;我只是旁观者。 但处理该故障的DBA工程师&#xff0c;思路很清晰&#xff0c;我非常受教&#xff01;在此也将经验分享。 目录 项目场景 问题分析 优化建议 项目场景 在某项目数据库运维群&#xff0c;有现场同事发了张报错截图如下…

数据库服务类--Redis--未授权访问终端Getshell

免责声明:本文仅做技术交流与学习. 目录 前提条件: windows上开启redis服务: Linux上创建&开启redis服务: 操作: 1-连接靶机redis 2-写入webshell 3-访问后门 redis--->webshell Redis未授权访问漏洞复现与利用 - 知乎 (zhihu.com) 前提条件: 端口开放(6379) 目录…

C++:特殊成员函数

构造函数、析构函数和拷贝构造函数是C类中的三种特殊成员函数&#xff0c;它们分别用于对象的初始化、清理和拷贝操作。 1.构造函数&#xff08;Constructor&#xff09;&#xff1a;构造函数在对象创建时自动调用&#xff0c;用于初始化对象的成员变量。它的名称与类名相同&a…

《动手学深度学习(Pytorch版)》Task01:初识深度学习——4.22打卡

深度学习介绍 AI地图 自然语言处理&#xff1a;起源于符号学&#xff0c;如机器翻译&#xff0c;人在几秒钟能反应过来&#xff0c;属于感知问题计算机视觉&#xff1a;图片由像素组成&#xff0c;难以用符号学解释&#xff0c;在图片中进行推理&#xff0c;大部分用概率模型或…

vue3组件封装系列-表单请求

我们在开发一些后台管理系统时&#xff0c;总是会写很多的列表查询页面&#xff0c;如果不封装组件&#xff0c;就会无限的复制粘贴&#xff0c;而且页面很冗余&#xff0c;正常情况下&#xff0c;我们都是要把组件进行二次封装&#xff0c;来达到我们想要效果。这里我分享一下…

uni-app 的 扩展组件(uni-ui) 与uView UI

uni-app 的 扩展组件&#xff08;uni-ui&#xff09; 与uView UI uni-ui 官方背景&#xff1a;组件集&#xff1a;设计风格&#xff1a;文档与支持&#xff1a;社区与生态&#xff1a; uView UI 第三方框架&#xff1a;组件集&#xff1a;设计风格&#xff1a;文档与支持&#…

ScriptableObject数据容器讲解

概述 是Unity提供的一个用于创建可重用的数据容器或逻辑的基类。 ScriptableObject 是继承自 UnityEngine.Object 的一个类&#xff0c;但与普通的 MonoBehaviour 不同&#xff0c;它不能附加到GameObject上作为组件。 相反&#xff0c;ScriptableObject 通常用于存储和管理…

【JavaSE启航篇 01】探索JavaSE:史上最强JavaSE学习路线图 知识图谱

【JavaSE启航篇 01】探索JavaSE&#xff1a;史上最强JavaSE学习路线图 &知识图谱 作者名称&#xff1a;纸飞机-暖阳 作者简介&#xff1a;专注于Java和大数据领域&#xff0c;致力于探索技术的边界&#xff0c;分享前沿的实践和洞见 文章专栏&#xff1a;JavaSE那些年专栏 …

数据库管理-第173期 OceanBase一体化Plus多模融合(20240422)

数据库管理173期 2024-04-22 数据库管理-第173期 OceanBase一体化Plus多模融合&#xff08;20240422&#xff09;1 架构简化2 不止融合2.1 行列混存2.2 多维使用2.3 多模JOIN 3 展望 数据库管理-第173期 OceanBase一体化Plus多模融合&#xff08;20240422&#xff09; 作者&…

3D Gaussian Splatting介绍

目录 一、概述二、基础介绍1. 多维高斯分布2. 将3D 高斯投影到2D像素平面3. 球谐函数4. Splatting and α \alpha α blending 三、整体流程四、 伪代码五、评价指标六、实验结果七、reference 一、概述 3D Gaussian Splatting和NeRF一样&#xff0c;主要用于新视图合成。 特…

eCharts 折线图 一段是实线,一段是虚线的实现效果

在lineStyle里写了不生效的话&#xff0c;可以尝试数据拼接 option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [150, 230, 224,218 ,,,],type: line},{data: [,,, 218, 135, 147, 260],type: line,lineStyl…