CountDownLatch详解以及用法示例

一、什么是CountDownLatch

CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓。

CountDownLatch的作用也是如此,在构造CountDownLatch(int count):的时候需要传入一个整数count,在这个整数“倒数”到0之前,主线程需要等待在门口,而这个“倒数”过程则是由各个执行线程驱动的,每个线程执行完一个任务“倒数”一次。

总结来说,CountDownLatch是Java中一个多线程工具类,用于控制线程的顺序,可以让主线程等待其他线程完成任务之后再继续执行,或者让多个线程之间相互等待。

二、主要方法

  1. CountDownLatch(int count):构造方法,创建一个新的 CountDownLatch 实例,用给定的计数初始化。参数 count 表示线程需要等待的任务数量。
int numberOfTasks = 5;
CountDownLatch latch = new CountDownLatch(numberOfTasks);
  1. void await():使当前线程等待,直到计数器值变为0,除非线程被 interrupted。如果计数器的值已经为0,则此方法立即返回。在实际应用中,通常在主线程中调用此方法,等待其他子线程完成任务。(会使线程休眠,直到countDownLatch的值递减到0,才会重新就绪)
latch.await();
  1. boolean await(long timeout, TimeUnit unit):使当前线程等待,直到计数器值变为0,或者指定的等待时间已到,或者线程被 interrupted。如果计数器的值已经为0,则此方法立即返回。
  • 参数 timeout 是指定的等待时间,
  • 参数 unit 是 timeout 的单位(如秒、毫秒等)。

此方法返回一个布尔值,表示在等待时间内计数器是否变为0。

latch.await(5, TimeUnit.SECONDS);

这里需要注意的是,await()方法并没有规定只能有一个线程执行该方法,如果多个线程同时执行await()方法,那么这几个线程都将处于等待状态,并且以共享模式享有同一个锁。

  1. void countDown():递减计数器的值。如果计数器的结果为0, 则释放所有等待的线程。在实际应用中,通常在线程完成任务后调用此方法。
latch.countDown();

这里需要注意的是,countDown()方法并没有规定一个线程只能调用一次,当同一个线程调用多次countDown()方法时,每次都会使计数器减一;

  1. long getCount():获取当前计数的值。返回当前 CountDownLatch 实例内部的计数值。
long remainingCount = latch.getCount();

三、优缺点

  • 优点
  1. 简化了线程间的通信和同步。在某些并发场景中,需要等待其他线程完成任务后才能继续执行,使用 CountDownLatch 可以简化这种操作,而不需要复杂的锁和等待/通知机制。
  2. 提高性能。由于 CountDownLatch 可以让线程在完成任务后立即递减计数值,而不需要等待其他线程完成任务,因此可以减少阻塞,提高程序运行性能。
  3. 支持灵活的计数。可以通过创建不同的 CountDownLatch 实例,实现对多个线程任务计数。
  • 缺点:
  1. 单次使用。CountDownLatch 的计数值无法重置。一旦计数值到达零,它就不能再被使用了。在需要重复使用的场景中,可以选用 CyclicBarrier 或 Semaphore。
  2. 没有返回值。CountDownLatch 无法获得执行任务的线程所返回的结果。如果需要收集线程执行结果,可以考虑使用 java.util.concurrent.Future 和 java.util.concurrent.ExecutorService。

四、使用场景

  1. 启动多个线程执行并行任务,主线程等待所有并行任务完成后继续执行。
    例如:在测试中,准备数据阶段,需要同时查询多个子系统的数据和处理,等待处理结束后再进行下一步操作。
  2. 控制线程的执行顺序。一个线程需要等待其他线程的结果或者完成任务后才能继续执行。
    例如:一个文件解压缩程序,首先需要下载文件,下载完成后解压文件。
  3. 实现一个计数器,允许一个或多个线程等待直到计数器为0。这对于在系统初始化时,需要等待资源加载或者初始化的场景十分有用。
    例如:等待加载配置文件、启动连接池等操作完成后才开始处理其他任务。

五、示例代码

一个简单示例代码

在这个例子中,创建了5个线程,并让每个线程睡眠1秒钟,表示完成一个任务。在每个线程完成任务后,调用了countDown()方法,计数器减1。

在主线程中,调用了await()方法,等待所有线程完成任务。当所有线程的计数器都减为0时,主线程才会继续执行,输出"All tasks done"。

import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {int n = 5; // 等待5个线程完成任务CountDownLatch countDownLatch = new CountDownLatch(n);for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " is working");Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " done");countDownLatch.countDown(); // 计数器减1} catch (InterruptedException e) {e.printStackTrace();}});t.start();}countDownLatch.await(); // 等待其他线程完成任务System.out.println("All tasks done");}
}

输出结果:

Thread-0 is working
Thread-1 is working
Thread-2 is working
Thread-3 is working
Thread-4 is working
Thread-2 done
Thread-1 done
Thread-0 done
Thread-4 done
Thread-3 done
All tasks done

启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行

package base.threadabout.multhread.countdownlatch;import java.util.concurrent.*;/*** CountDownLatch常用方法:await(),await(long,TimeUnit),countDown()* await()会使线程休眠,直到countDownLatch的值递减到0,才会重新就绪* await(long, TimeUnit) 休眠,直到countDownLatch的值递减到0或休眠时间结束* 大概作用:等所有线程&某些线程都执行完了,再统一执行某个具体功能*/
public class MainLoadingService {public static void main(String[] args) throws InterruptedException {CountDownLatch cdl = new CountDownLatch(5);ExecutorService pool = Executors.newFixedThreadPool(5);for (int i = 0; i < 5; i++) {Loading runnable = new Loading(cdl);pool.execute(runnable);}// 线程全部跑完的标志System.out.println("等待子线程加载组件...");cdl.await();System.out.println("所有组件加载完毕,继续执行...");pool.shutdown();}
}class Loading implements Runnable {private CountDownLatch countDownLatch;public Loading(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {// 处理业务String name = Thread.currentThread().getName();System.out.println("子线程:" + name + "正在加载组件...");// 业务处理完毕,countDownLatch-1countDownLatch.countDown();}
}

结果:

等待子线程加载组件...
子线程:pool-1-thread-1正在加载组件...
子线程:pool-1-thread-2正在加载组件...
子线程:pool-1-thread-3正在加载组件...
子线程:pool-1-thread-4正在加载组件...
子线程:pool-1-thread-5正在加载组件...
所有组件加载完毕,关闭线程池pool...

示例

主线程定义new CountDownLatch(1)。每个子线程先执行await(),进入等待。等待所有子线程都开启,主线程执行countDown(),能确保所有子线程同时开始处理任务。

类似于赛跑,子线程是运动员,await是运动员的预备阶段,主线程是裁判,countDown是裁判的发令枪。枪响运动员才能跑。

package base.threadabout.multhread.countdownlatch;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class RaceGame {public static void main(String[] args) throws InterruptedException {ExecutorService pool = Executors.newFixedThreadPool(5);CountDownLatch countDownLatch = new CountDownLatch(1);for (int i = 0; i < 5; i++) {Player player = new Player(i, countDownLatch);pool.execute(player);}Thread.sleep(1000);System.out.println("所有选手各就位.....GO!");countDownLatch.countDown();pool.shutdown();}static class Player implements Runnable{private int id;private CountDownLatch countDownLatch;public Player(int id, CountDownLatch countDownLatch) {this.id = id;this.countDownLatch = countDownLatch;}@Overridepublic void run() {System.out.println("参赛选手[" + id +"]号,准备就绪...");try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("参赛选手[" + id +"]号,到达终点...");}}
}

结果:

参赛选手[0]号,准备就绪...
参赛选手[1]号,准备就绪...
参赛选手[2]号,准备就绪...
参赛选手[3]号,准备就绪...
参赛选手[4]号,准备就绪...
所有选手各就位.....GO!
参赛选手[1]号,到达终点...
参赛选手[3]号,到达终点...
参赛选手[0]号,到达终点...
参赛选手[2]号,到达终点...
参赛选手[4]号,到达终点...

示例代码

下面的示例展示了一个简单的网站爬虫,它使用 CountDownLatch 在主线程中等待其他爬虫线程完成任务。在这个例子中,我们要爬取一组网站的内容,在主线程中等待所有爬虫任务完成。

首先,我们创建一个 URLs 列表,包含多个网站 URL。

然后,我们使用 CountDownLatch 实例 latch 来跟踪待完成的爬虫任务数量。

接着,我们遍历 URL 列表,为每个 URL 创建一个新的 Crawler 线程。Crawler 类实现了 Runnable 接口,用于读取指定 URL 的网页内容。在完成任务后,它调用 latch.countDown() 方法减少计数值。

最后,在主线程中,我们调用 latch.await() 方法等待所有爬虫线程完成任务。当所有任务完成时,打印一条消息表示爬虫任务已完成。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class WebCrawler {private static class Crawler implements Runnable {private final String url;private final CountDownLatch latch;public Crawler(String url, CountDownLatch latch) {this.url = url;this.latch = latch;}@Overridepublic void run() {try {URL urlObject = new URL(url);BufferedReader in = new BufferedReader(new InputStreamReader(urlObject.openStream()));String inputLine;StringBuilder content = new StringBuilder();while ((inputLine = in.readLine()) != null) {content.append(inputLine);content.append("\n");}in.close();System.out.println("爬取 " + url + " 成功, 内容大小: " + content.length() + " 字符");} catch (Exception e) {System.err.println("爬取 " + url + " 失败, 原因: " + e.getMessage());} finally {latch.countDown();}}}public static void main(String[] args) throws InterruptedException {List<String> urls = new ArrayList<>();urls.add("https://github.com/");urls.add("https://stackoverflow.com/");urls.add("https://www.zhihu.com/");urls.add("https://www.reddit.com/");urls.add("https://www.linkedin.com/");CountDownLatch latch = new CountDownLatch(urls.size());System.out.println("开始爬虫任务...");for (String url : urls) {new Thread(new Crawler(url, latch)).start();}latch.await();System.out.println("所有爬虫任务都已完成!");}
}

运行结果

开始爬虫任务...
爬取 https://www.zhihu.com/ 成功, 内容大小: 37783 字符
爬取 https://github.com/ 成功, 内容大小: 227576 字符
爬取 https://stackoverflow.com/ 成功, 内容大小: 171290 字符
爬取 https://www.linkedin.com/ 成功, 内容大小: 12603 字符
爬取 https://www.reddit.com/ 失败, 原因: Read timed out
所有爬虫任务都已完成!

稍复杂点的示例代码

在这个例子中,我们将模拟一个简单的赛车游戏,

  • 其中有一个倒计时开始。
  • 一旦倒计时结束,赛车就开始比赛,
  • 当所有赛车完成比赛时,主线程打印一条消息。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;public class CountDownLatchAdvancedDemo {public static void main(String[] args) throws InterruptedException {int numberOfRacers = 5;CountDownLatch startSignal = new CountDownLatch(1);CountDownLatch finishSignal = new CountDownLatch(numberOfRacers);// 创建赛车线程for (int i = 0; i < numberOfRacers; i++) {//这里虽然start,但是由于前面new了startSignal,并且实现类中await的影响会等待new Thread(new Racer(startSignal, finishSignal)).start();}// 模拟倒计时System.out.println("倒计时开始...");for (int i = 3; i > 0; i--) {System.out.println("倒计时: " + i);TimeUnit.SECONDS.sleep(1);}System.out.println("比赛开始!");startSignal.countDown(); // 启动信号// 等待所有赛车完成比赛finishSignal.await();System.out.println("所有赛车都完成了比赛!");}static class Racer implements Runnable {private CountDownLatch startSignal;private CountDownLatch finishSignal;public Racer(CountDownLatch startSignal, CountDownLatch finishSignal) {this.startSignal = startSignal;this.finishSignal = finishSignal;}@Overridepublic void run() {try {// 等待开始信号startSignal.await();// 正在比赛System.out.println(Thread.currentThread().getName() + " 开始比赛...");Thread.sleep((long) (Math.random() * 10000));System.out.println(Thread.currentThread().getName() + " 完成比赛!");} catch (InterruptedException e) {e.printStackTrace();} finally {// 完成比赛后,递减完成信号计数finishSignal.countDown();}}}
}

在这个例子中,我们创建了两个 CountDownLatch:

  • 一个用于开始信号 (startSignal),
  • 另一个用于完成信号 (finishSignal)。创建赛车线程时,它们都需要等待开始信号。

当倒计时结束时,调用 startSignal.countDown(),开始信号变为0,并表示比赛开始。

每个线程在模拟赛车完成比赛后,调用 finishSignal.countDown() 减少完成信号计数。

主线程使用 finishSignal.await() 等待所有赛车线程都完成比赛。当计数值变为 0 时,主线程将打印一条消息表示所有赛车都完成了比赛。

运行结果:


倒计时开始...
倒计时: 3
倒计时: 2
倒计时: 1
比赛开始!
Thread-4 开始比赛...
Thread-2 开始比赛...
Thread-0 开始比赛...
Thread-1 开始比赛...
Thread-3 开始比赛...
Thread-4 完成比赛!
Thread-1 完成比赛!
Thread-0 完成比赛!
Thread-2 完成比赛!
Thread-3 完成比赛!
所有赛车都完成了比赛!

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

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

相关文章

PortSwigger Access Control

lab1: Unprotected admin functionality 访问robots.txt 进了删除即可 lab2: Unprotected admin functionality with unpredictable URL 访问admin-d0qwj5 lab3: User role controlled by request parameter 发现Cookie中存在判断是否为admin lab4: User role can be modifie…

gitattributes配置文件的作用

0 Preface/Foreword 0.1 基本概念 Git版本管控工具功能强大&#xff0c;在使用过程中&#xff0c;在多人合作的项目开发过程中&#xff0c;经常会遇到提交代码时出现的warning提醒&#xff0c;尤其是换行符。 Linux/Unix/Mac OS操作系统的换行符使用LF符号&#xff08;\n&am…

逆向P1P2总结

字节八位 word 16位 deword 32 位 00 00 00 e8 是存储32位信息的起点 不是程序运行的起点 为什么电脑有32位与64位之分 寻址宽度 以字节为单位 0xfffffff 1 就是最大容量 转为十进制为 4294967296 / 1024 &#xff08;k&#xff09;/1024 &#xff08;kb&#xff09;/ 1…

web功能实例 - Canvas裁剪工具

嗯,手撸官方文档2天&#xff0c;发现没啥用&#xff0c;尤其是动画,那种计算出来的&#xff0c;根本想不到。因此学着学了抱着要做个东西的想法,去网上找相关案例,最终做出了这个裁剪工具。 PS :先说一下思路: 核心实现有3个canvas图层, 其中一个负责图片的预览。另外2个叠加到…

【华为机试】2023年真题B卷(python)-发广播

一、题目 题目描述&#xff1a; 某地有N个广播站&#xff0c;站点之间有些有连接&#xff0c;有些没有。有连接的站点在接受到广播后会互相发送。 给定一个N*N的二维数组matrix,数组的元素都是字符’0’或者’1’。 matrix[i][j]‘1’,则代表i和j站点之间有连接&#xff0c;mat…

web前端游戏项目-辨色大比拼【附源码】

web前端游戏项目-辨色大比拼【附源码】 《辨色大比拼》是一个旨在测试和提升玩家颜色识别能力的在线游戏。在游戏中&#xff0c;玩家将通过辨识颜色来解谜并推进游戏进程。辨色大比拼也是一个寓教于乐的游戏&#xff0c;它不仅提供了一个有趣的辨色挑战&#xff0c;还能帮助玩…

vue组件的类型及特点

vue组件的类型及特点 如果想要对组件进行类型划分&#xff0c;从实现的功能以及所具备的特点来划分&#xff0c;大致可以归纳为&#xff1a;动态组件、缓存组件、异步组件、函数式组件 JSX、递归组件等 动态组件&#xff1a;通过动态确定要显示的组件, is指定要显示组件的组件…

leetcode2两数加和问题(链表)

题目思路&#xff1a; ①创建一个int类型的局部变量&#xff0c;用来存储两个结点的Val值。 ②判断该Val值与10求余(mod)后是否大于0,如果大于0, 则需要在下一个结点进位。 ③最关键的步骤&#xff1a;实现l1&#xff0c;l2结点数值相加后构建新的存储求和后的结点&#xff0…

.NET Framework 对应系统版本汇总

复选标记图标 ✔️ 表示默认安装 .NET Framework 的操作系统版本。加号图标 ➕ 表示 .NET Framework 未安装但可以安装的操作系统版本。星号 * 表示必须在控制面板中&#xff08;如果是 Windows Server&#xff0c;则通过服务器管理器&#xff09;启用 .NET Framework&#xff…

IntelliJ IDEA插件

插件安装目录&#xff1a;C:\Users\<username>\AppData\Roaming\JetBrains\IntelliJIdea2021.2\plugins aiXcoder Code Completer&#xff1a;代码补全 Bookmark-X&#xff1a;书签分类 使用方法&#xff1a;鼠标移动到某一行&#xff0c;按ALT SHIFT D

Iptables深度解析:四表五链与动作参数

Iptables是Linux系统中强大的网络流量控制工具&#xff0c;它通过四种主要的表&#xff08;raw、mangle、nat、filter&#xff09;和五条链&#xff08;INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING&#xff09;来实现对数据包的过滤、修改和地址转换。 表的概述 在ipta…

修改PCIE 设备控制寄存器DevCtl2参数

如何修改PCIE 设备控制寄存器DevCtl2参数&#xff1f; 参考书籍&#xff1a;PCI_Express_Base_Spec 如图所示&#xff1a;输入Lspci -s 00&#xff1a;08&#xff1a;00 -vvv|grep - i deve 输出DevCap、DevCtl、DevCap2、DevCtl2参数&#xff0c;本节重点分析UEFI BIOS怎么设置…

FPGA分频电路设计(2)

实验要求&#xff1a; 采用 4 个开关以二进制形式设定分频系数&#xff08;0-10&#xff09;&#xff0c;实现对已知信号的分频。 类似实验我之前做过一次&#xff0c;但那次的方法实在是太笨了&#xff1a; 利用VHDL实现一定系数范围内的信号分频电路 需要重做以便将来应对更…

JUC并发编程 09——队列同步器AQS

目录 一.Lock接口 1.1Lock的使用 1.2Lock接口提供的 synchronized 不具备的主要特性 1.3Lock接口的所有方法 二.队列同步器(AQS) 2.1队列同步器的接口与示例 2.2AQS实现源码分析 ①同步队列 ②获取锁 ③释放锁 一.Lock接口 说起锁&#xff0c;你肯定会想到 synchron…

Android Studio 如何实现软件英文变中文教程

目录 前言 一、确认版本号 二、下载汉化包 三、汉化包安装 四、如何实现中英文切换 五、更多资源 前言 Android Studio是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;用于开发Android应用程序。默认情况下&#xff0c;Android Studio的界面和…

js中Math.min(...arr)和Math.max(...arr)的注意点

当arr变量为空数组时&#xff0c;这两个函数和不传参数时的结果是一样的 Math.max() // -Infinity Math.max(...[]) // -InfinityMath.min() // Infinity Math.min(...[]) // Infinity

如何编写高效清晰的嵌入式C程序

作为嵌入式工程师&#xff0c;怎么写出效率高、思路清晰的C语言程序呢? 要用C语言的思维方式来进行程序的构架构建 要有良好的C语言算法基础&#xff0c;以此来实现程序的逻辑构架 灵活运用C语言的指针操作 虽然看起来以上的说法很抽象&#xff0c;给人如坠雾里的感觉&…

RocketMQ与SpringBoot实际项目中使用

消息生产者 1&#xff09;添加依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version> </parent><properties><rocketm…

data 为什么是一个函数

在前端开发中&#xff0c;特别是在使用Vue.js框架时&#xff0c;"data" 通常是一个函数&#xff0c;而不是一个变量。这是因为在Vue组件中&#xff0c;"data" 是一个函数&#xff0c;用于返回包含组件状态的对象。这样做有以下几个原因&#xff1a; 避免数…

3.架构设计系列:高并发系统的设计目标

架构设计系列文章 架构设计系列&#xff1a;什么是架构设计架构设计系列&#xff1a;几个常用的架构设计原则 一、如何理解高并发&#xff1f; 高并发&#xff0c;往往意味着大的流量&#xff0c;而大流量必然会对系统带来冲击。面对这种大流量的冲击&#xff0c;有的系统抗住…