当前位置: 首页 > news >正文

CountDownLatch的定义、使用 、原理

一、定义
CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。

注意:java中还有一个同步工具类叫做CyclicBarrier,他的作用和CountDownLatch类似。同样是等待其他线程都完成了,才可以进行下一步操作,我们再举一个例子,在打王者的时候,在开局前所有人都必须要加载到100%才可以进入。否则所有玩家都相互等待。

我们看一下区别:

CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。关键点其实就在于那N个线程(1)CountDownLatch里面N个线程就是学生,学生做完了试卷就可以走了,不用等待其他的学生是否完成(2)CyclicBarrier 里面N个线程就是所有的游戏玩家,一个游戏玩家加载到100%还不可以,必须要等到其他的游戏玩家都加载到100%才可以开局

现在应该理解CountDownLatch的含义了吧,下面我们使用一个代码案例来解释。

二、使用
我们使用学生考试的案例来进行演示:

我们定义了一个CountDownLatch,并设置其值为2。有两个学生使用两个线程来表示,然后依次执行。最后老师线程(main线程)在学生线程都执行完了才可以执行。我们来运行一边看看结果。

  
public  void mysqlTest() {
CountDownLatch  countDownLatch=new CountDownLatch(2);System.out.println("全班同学开始考试,一共2个同学!");new Thread(()->{System.out.println("第一个同学交卷!countDownLatch-1");countDownLatch.countDown();}).start();new Thread(()->{System.out.println("第二个同学交卷!countDownLatch-1");countDownLatch.countDown();}).start();try {countDownLatch.await();} catch (Exception e) {e.printStackTrace();}System.out.println("老师清点完试卷,只要有一个学生未交卷,只要countDownLatch不为0,所有考试不得离场!");}

在上面,我们定义了一个CountDownLatch,并设置其值为2。有两个学生使用两个线程来表示,然后依次执行。最后老师线程(main线程)在学生线程都执行完了才可以执行。

在这里插入图片描述

在上面我们的等待线程时老师(main线程),CountDownLatch从2见减到0,主线程才执行。
下面我们对这个countDownLatch分析一下。为什么具有上面的特点。

三、原理
在上面我们看到,CountDownLatch主要使用countDown方法进行减1操作,使用await方法进行等到操作。我们进入到源码中看看。本源码基于jdk1.8。特在此说明。

1、countDown原理

/*** Decrements the count of the latch, releasing all waiting threads if* the count reaches zero.** <p>If the current count is greater than zero then it is decremented.* If the new count is zero then all waiting threads are re-enabled for* thread scheduling purposes.** <p>If the current count equals zero then nothing happens.*/
public void countDown() {sync.releaseShared(1);
}

CountDownLatch里面保存了一个count值,通过减1操作,直到为0时候,等待线程才可以执行。而且通过源码也可以看到这个countDown方法其实是通过sync调用releaseShared(1)来完成的。

OK。到了这一步就疑问,sync是什么,releaseShared方法又是如何实现的。我们不妨接着看源码,在CountDownLatch的开头我们找到了答案,原来这个sync在这里定义了。

public class CountDownLatch {/*** Synchronization control For CountDownLatch.* Uses AQS state to represent count.*/private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}}

在这里我们发现继承了AbstractQueuedSynchronizer(AQS)。AQS的其中一个作用就是维护线程状态和获取释放锁。在这里也就是说CountDownLatch使用AQS机制维护锁状态。而releaseShared(1)方法就是释放了一个共享锁。

现在理解了吧,底层使用AQS机制调用releaseShared方法释放一个锁资源。那么等待的方法是如何实现的呢?

2、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));}

这俩方法都是让线程等待,第一个没有实现限制,第二个有时间限制

(1)await()

await()底层主要是acquireSharedInterruptibly方法实现的,继续跟进去看看。

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

这里面有两个if语句,首先第一个判断是否被中断,如果被中断了,那就抛出中断异常。然后判断当前是否还有线程未执行,如果有那就,那就执行doAcquireSharedInterruptibly方法继续等待。

protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}

上面函数的意思:

1、这是aqs里的方法 2、arg在这里调用的是1,标识countDown是否减少到了0 3、如果到了0,说明满足要求,返回1不在等待
4、如果未达到0,说明还有线程未执行,必须等待
5、执行结束才可以,返回-1,此时小于0执行doAcquireSharedInterruptibly方法

下面我们就来看看这个doAcquireSharedInterruptibly是如何实现的。

 private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

大致意思:他会用一个一个的节点将线程串起来 等达到条件后再一个一个的唤醒。核心就是第三行的addWaiter函数。我们可以再跟进去看看吧。

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;
}

你会发现这里面也使用了CAS机制。而且就是使用链表穿起来的。

(2) await(long timeout, TimeUnit unit)

这个方法的意思是等待指定的时间,如果还有线程没执行完,那就接着执行。就好比考完试了,还有同学没交试卷,此时因为到时间了。不管三七二十一也不管剩下的同学是否提交,直接就走了。其底层是通过Sync的tryAcquireSharedNanos方法实现的,我们接着进入到源码中看看。

 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquireShared(arg) >= 0 ||doAcquireSharedNanos(arg, nanosTimeout);}

在这里皮球又一次被踢走了,真正实现的其实就是doAcquireSharedNanos方法,tryAcquireShared方法主要是判断是否当前满足wait的条件。我们接着看。

 private boolean doAcquireSharedNanos(int arg, long nanosTimeout)throws InterruptedException {if (nanosTimeout <= 0L)return false;final long deadline = System.nanoTime() + nanosTimeout;final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return true;}}nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L)return false;if (shouldParkAfterFailedAcquire(p, node) &&nanosTimeout > spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);if (Thread.interrupted())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

上面的代码看似长,最核心的就是for循环里面的,最主要的意思就是如果当前还有线程未执行而且过了超时时间,那就直接执行等待线程就好了,不再等了。也就是我在指定的时间内你没执行完我等着你,要是超了这个时间点我就不管了。

对于CountDownLatch来说原理主要还是通过源码来认识。不过CountDownLatch看起来虽然很好用,也有很多不足之处,比如说CountDownLatch是一次性的 , 计数器的值只能在构造方法中初始化一次 , 之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后 , 它不能再次被使用。

相关文章:

CountDownLatch的定义、使用 、原理

一、定义 CountDownLatch的作用很简单&#xff0c;就是一个或者一组线程在开始执行操作之前&#xff0c;必须要等到其他线程执行完才可以。我们举一个例子来说明&#xff0c;在考试的时候&#xff0c;老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程&#xff…...

《Terraform 101 从入门到实践》 Terraform在公有云Azure上的应用

《Terraform 101 从入门到实践》这本小册在南瓜慢说官方网站和GitHub两个地方同步更新&#xff0c;书中的示例代码也是放在GitHub上&#xff0c;方便大家参考查看。 简介 Azure是微软的公有云&#xff0c;它提供了一些免费的资源&#xff0c;具体可以查看&#xff1a; https:/…...

别具一格,原创唯美浪漫情人节表白专辑,(复制就可用)(html5,css3,svg)表白爱心代码(3)

别具一格&#xff0c;原创唯美浪漫情人节表白专辑&#xff0c; (复制就可用)&#xff08;html5,css3,svg)表白爱心代码(3) 目录 款式三&#xff1a;心形实时显示认识多长时间桃花飞舞&#xff08;猫咪&#xff09;款 1、拷贝完整源代码 2、拷贝完整js代码 3、修改时间 4、…...

Linux 删除修改日期大于某一天的文件

在服务器运维过程中,我们往往会产生大量的日志文件. 如果日志文件命名能看出日志产生的时间,这些文件是很好删除的. 但有时,我们可能有成千上万的没有命名规律日志文件 下面的方法可以根据日志最后修改时间 批量删除这些文件 先给出完整命令: find /mydir -mtime 10 -name &…...

【算法题】1845. 座位预约管理系统

插&#xff1a; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家一起学习鸭~~~ 题目&#xff1a; 请你设计一个管理 n 个座位预约的系…...

【专业认知】保研北大金融 / 入职腾讯产品经理

2023.02.11 一. 朱博文学长分享——关于大学生活的一点思考 1. 自我介绍 大数据18级 经济学双学位 保研至北大金融硕士 “多思考、多感受、兼听则明” 2. 大学生活 2.1 为什么要上大学 1&#xff1a;追求美好生活的需要 “美好”难以量化&#xff0c;因为每个人对生活…...

OpenHarmony使用Socket实现一个UDP客户端详解

一、前言 我们在这里介绍Socket的使用,是为了后面的一篇文章实现设备配网做铺垫。 二、示例详解 点击获取BearPi-HM_Nano源码 ,以D3_iot_udp_client为例: 示例本身很简单,只需要修改 udp_client_demo.c 的2处代码,就能测试了: //连接WIFI,参数1是:WIFI名称,参数2是:…...

使用VUE自定义组件封装部门选择功能

背景 照惯例&#xff0c;先交待下背景&#xff0c;从真实需求出发&#xff0c;讲述实现效果、设计思路和实现方式。 软件系统中&#xff0c;会有一些常见常用的选择功能&#xff0c;如部门选择、人员选择等&#xff0c;用于填报表单&#xff0c;使用频率很高。直接使用一方面会…...

C语言基础应用(一)数据类型

一、数据类型 1、数据类型的分类 2、常量 常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。 2.1 常量举例 // 整型常量 举例 /*718 十进制0213 八进制0x4b 十六进制30u 无符号整数30l 长整型30ul 无符号长整型*/ // 浮点常量…...

算法笔记(三)—— 桶排序及排序总结

堆 逻辑上是一棵完全二叉树&#xff08;依次遍满或者全满&#xff09;。 数组可以转为完全二叉树&#xff0c;完全二叉树某结点左孩子(2*i1)&#xff0c;右孩子(i*22)&#xff0c;父结点((i-1/)2)&#xff0c;根节点的父还是自己。 如何将数组转化为堆&#xff08;大根堆&…...

Linux入门篇(一)

Linux前言Linux初探Linux内核GNU实用工具shellLinux发行版bash shell 基础Linux文件系统Linux文件操作命令前言 在阅读诸如docker之类的书的时候&#xff0c;经常碰到Linux的知识。同时&#xff0c;大部分的盲区也是在Linux方面。因此就想稍微了解一下这个广为人使用的操作系统…...

HTTPSHandler SSL Error

我在服务器ubuntu中&#xff0c;尝试使用pip3&#xff0c;但是出现下面的报错 ImportError: cannot import name HTTPSHandler 通过查询资料&#xff0c;发现报错的原因是&#xff0c;该pip3.5中没有安装好openssl. 我尝试在python3.5中使用import ssl, 确实是会显示下面的报错…...

基于Android的高校食堂餐厅配送系统

需求信息&#xff1a; 商家客户端&#xff1a; 1&#xff1a;登录注册&#xff1a;用户可以通过自己的信息进行账号的注册 2&#xff1a;发布菜单&#xff1a;发布自己经营的美食信息 3&#xff1a;用户订单&#xff1a;查看用户的购买订单 4&#xff1a;订单配送&#xff1a;对…...

Java设计模式-02工厂模式

为什么需要工厂模式&#xff0c;其作用什么&#xff1f;如何实现&#xff0c;代码演示解析优缺点。Q1:为什么需要工厂模式&#xff1f;工厂模式的作用(优点)是什么&#xff1f; 解耦。把对象的创建和使用的过程分开。就是Class A 想调用 Class B &#xff0c;那么A只是调用B的…...

AXI-Lite 学习笔记

AXI-Lite 学习笔记 参考 FPGA&#xff1a;AXI_Lite总线基础2-1]、第二节 AXI总线介绍、ZYNQ PL与PS交互专题_哔哩哔哩_bilibili AXI-Lite总线系列1 - 基础知识_哔哩哔哩_bilibili AXI4 介绍 AXI4 是ARM公司提出的一种片内总线&#xff0c;描述了主从设备之间的数据传输方式。主…...

77页智慧城市顶层设计方案

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a;篇幅有限&#xff0c;无法完全展…...

JavaWeb--MavenMybatis基础

JavaWeb--Maven&Mybatis基础1 Maven1.1 Maven简介1.1.1 Maven模型1.1.2 仓库1.2 Maven基本使用1.2.1 Maven 常用命令1.2.2 Maven 生命周期1.3 IDEA使用Maven1.3.1 IDEA配置Maven环境1.3.2 Maven 坐标详解1.3.3 IDEA 创建 Maven项目1.3.4 IDEA 导入 Maven项目1.4 依赖管理1.…...

博客系统--测试用例编写

目录一&#xff0c;整体概览1.1&#xff0c;登录页面测试用例1.2&#xff0c;注册页面测试用例1.3&#xff0c;发布博客功能测试1.4&#xff0c;删除博客功能测试二&#xff0c;具体设计2.1&#xff0c;注册页面测试--等价类法2.2&#xff0c;删除博客功能测试--判定表法一&…...

SpringCloud Alibaba

文章目录&#x1f68f; 第十七章 SpringCloud Alibaba入门简介&#x1f6ac; 一、为什么使用Alibaba&#x1f6ad; 1、spring netflix进入维护模式&#x1f6ad; Spring cloud alibaba&#x1f6ac; 二、如何使用&#xff1f;&#x1f6ac; 三、版本对应&#x1f68f; 第十八章…...

地平线slam算法岗位 面试分享

本专栏分享 计算机小伙伴秋招春招找工作的面试经验和面试的详情知识点 专栏首页:秋招算法类面经分享 主要分享计算机算法类在面试互联网公司时候一些真实的经验 小伙伴自我介绍: 写在前面,南京某炮专,研二上阶段,简历写了两个竞赛和一个项目,一个机器人相关的二等奖,一个…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...

FOPLP vs CoWoS

以下是 FOPLP&#xff08;Fan-out panel-level packaging 扇出型面板级封装&#xff09;与 CoWoS&#xff08;Chip on Wafer on Substrate&#xff09;两种先进封装技术的详细对比分析&#xff0c;涵盖技术原理、性能、成本、应用场景及市场趋势等维度&#xff1a; 一、技术原…...

python打卡第47天

昨天代码中注意力热图的部分顺移至今天 知识点回顾&#xff1a; 热力图 作业&#xff1a;对比不同卷积层热图可视化的结果 def visualize_attention_map(model, test_loader, device, class_names, num_samples3):"""可视化模型的注意力热力图&#xff0c;展示模…...