CountDownLatch 详细介绍

CountDownLatch 是 Java 中 java.util.concurrent 包提供的一个同步工具类,用于协调多个线程之间的执行顺序。它允许一个或多个线程等待,直到其他线程完成一组操作后继续执行。CountDownLatch 是一种倒计数锁存器,通过设置一个初始计数器值,线程可以通过调用 countDown() 方法递减计数器,当计数器达到 0 时,等待的线程会被唤醒。

主要特点
  1. 一次性使用CountDownLatch 是一次性工具,一旦计数器减到 0,它不能被重置或重用。如果需要可重置的机制,可以考虑使用 CyclicBarrier
  2. 线程等待:一个或多个线程可以通过 await() 方法等待计数器归零。
  3. 计数器递减:通过 countDown() 方法,线程可以减少计数器的值。
  4. 线程安全CountDownLatch 是线程安全的,适合多线程并发场景。
  5. 灵活性:可以用于等待一组任务完成后再执行后续逻辑,或者让主线程等待多个子线程完成。
工作原理
  • CountDownLatch 内部维护一个计数器,初始化时指定一个正整数值。
  • 调用 countDown() 方法会将计数器减 1,直到计数器为 0。
  • 调用 await() 方法的线程会阻塞,直到计数器为 0,所有等待的线程会被唤醒。
  • 使用基于 AQS(AbstractQueuedSynchronizer)的同步机制,确保线程安全和高效的等待/通知机制。
常见方法
  • CountDownLatch(int count):构造方法,初始化计数器为指定的值 count
  • void countDown():将计数器减 1,如果计数器达到 0,唤醒所有等待的线程。
  • void await():使当前线程阻塞,直到计数器归零。
  • boolean await(long timeout, TimeUnit unit):使当前线程阻塞,直到计数器归零或超时,返回是否成功(true 表示计数器归零,false 表示超时)。
  • long getCount():返回当前计数器的值。

使用场景

  1. 等待多个线程完成:主线程等待多个子线程完成任务后再继续执行。
  2. 并行任务协调:在任务分解为多个子任务时,确保所有子任务完成后再进行汇总。
  3. 启动信号:确保多个线程同时开始执行(通过设置计数器为 1,主线程调用 countDown() 触发所有线程)。
  4. 测试并发场景:在性能测试中,模拟多个线程同时执行某个任务。

CountDownLatch 示例代码

以下是几个使用 CountDownLatch 的实际例子,展示其在不同场景下的应用。

示例 1:主线程等待多个子线程完成
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CountDownLatchExample1 {public static void main(String[] args) throws InterruptedException {int threadCount = 3;// 创建一个计数器,初始值为3CountDownLatch latch = new CountDownLatch(threadCount);// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(threadCount);// 提交三个任务for (int i = 1; i <= threadCount; i++) {final int taskId = i;executor.submit(() -> {try {System.out.println("任务 " + taskId + " 开始执行");Thread.sleep((int) (Math.random() * 1000)); // 模拟任务耗时System.out.println("任务 " + taskId + " 完成");latch.countDown(); // 计数器减1} catch (InterruptedException e) {e.printStackTrace();}});}// 主线程等待所有任务完成System.out.println("主线程等待所有任务完成...");latch.await();System.out.println("所有任务已完成,主线程继续执行");// 关闭线程池executor.shutdown();}
}

输出示例

主线程等待所有任务完成...
任务 1 开始执行
任务 2 开始执行
任务 3 开始执行
任务 1 完成
任务 3 完成
任务 2 完成
所有任务已完成,主线程继续执行

解释

  • 创建一个 CountDownLatch,初始计数器为 3。
  • 三个线程分别执行任务,并在完成后调用 countDown() 减少计数器。
  • 主线程调用 await(),阻塞直到计数器为 0。
  • 当所有线程完成任务后,计数器归零,主线程继续执行。
示例 2:模拟并发任务启动

这个例子展示如何使用 CountDownLatch 让多个线程同时开始执行任务。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CountDownLatchExample2 {public static void main(String[] args) throws InterruptedException {int threadCount = 5;// 创建一个计数器,初始值为1CountDownLatch startSignal = new CountDownLatch(1);// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(threadCount);// 提交5个任务,等待启动信号for (int i = 1; i <= threadCount; i++) {final int workerId = i;executor.submit(() -> {try {System.out.println("工人 " + workerId + " 准备就绪");startSignal.await(); // 等待启动信号System.out.println("工人 " + workerId + " 开始工作");Thread.sleep((int) (Math.random() * 1000)); // 模拟工作System.out.println("工人 " + workerId + " 完成工作");} catch (InterruptedException e) {e.printStackTrace();}});}// 主线程模拟准备时间Thread.sleep(1000);System.out.println("所有工人准备就绪,主线程发出启动信号!");startSignal.countDown(); // 发出启动信号// 关闭线程池executor.shutdown();}
}

输出示例

工人 1 准备就绪
工人 2 准备就绪
工人 3 准备就绪
工人 4 准备就绪
工人 5 准备就绪
所有工人准备就绪,主线程发出启动信号!
工人 1 开始工作
工人 2 开始工作
工人 3 开始工作
工人 4 开始工作
工人 5 开始工作
工人 3 完成工作
工人 1 完成工作
工人 5 完成工作
工人 2 完成工作
工人 4 完成工作

解释

  • 使用 CountDownLatch 的计数器为 1,模拟一个启动信号。
  • 所有工人线程调用 await() 等待主线程的信号。
  • 主线程调用 countDown() 后,所有工人线程几乎同时开始工作。
示例 3:带超时的等待

这个例子展示如何使用带超时的 await 方法。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class CountDownLatchExample3 {public static void main(String[] args) throws InterruptedException {int threadCount = 2;CountDownLatch latch = new CountDownLatch(threadCount);ExecutorService executor = Executors.newFixedThreadPool(threadCount);// 提交两个任务executor.submit(() -> {try {System.out.println("任务 1 开始执行");Thread.sleep(1000); // 模拟耗时任务System.out.println("任务 1 完成");latch.countDown();} catch (InterruptedException e) {e.printStackTrace();}});executor.submit(() -> {try {System.out.println("任务 2 开始执行");Thread.sleep(3000); // 模拟更长的耗时任务System.out.println("任务 2 完成");latch.countDown();} catch (InterruptedException e) {e.printStackTrace();}});// 主线程等待,最多等待2秒System.out.println("主线程等待,最多2秒...");boolean completed = latch.await(2, TimeUnit.SECONDS);if (completed) {System.out.println("所有任务在2秒内完成");} else {System.out.println("超时,部分任务未完成");}executor.shutdown();}
}

输出示例

主线程等待,最多2秒...
任务 1 开始执行
任务 2 开始执行
任务 1 完成
超时,部分任务未完成
任务 2 完成

解释

  • 主线程设置了 2 秒的超时时间,通过 await(2, TimeUnit.SECONDS)
  • 任务 1 在 1 秒内完成,但任务 2 需要 3 秒,因此主线程在超时后继续执行,输出“部分任务未完成”。

CountDownLatch 与其他工具的对比

  1. 与 CyclicBarrier 的区别
    • CountDownLatch 是一次性的,计数器归零后无法重用;CyclicBarrier 可以重置并重复使用。
    • CountDownLatch 通常用于主线程等待子线程;CyclicBarrier 更适合多个线程相互等待。
  2. 与 Semaphore 的区别
    • Semaphore 用于控制资源访问的并发数;CountDownLatch 用于协调线程的完成顺序。
  3. 与 wait/notify
    • CountDownLatch 提供了更简单、更高级的同步机制,基于 AQS 实现,性能更优。

使用建议

  • 适用场景:当需要等待一组任务完成后再执行后续逻辑,或需要多个线程同时开始时。
  • 不适用场景:如果需要重复使用计数器,建议使用 CyclicBarrier;如果需要控制并发访问量,建议使用 Semaphore
  • 注意事项
    • 确保初始计数器值合理,过高的计数可能导致线程长时间等待。
    • countDown() 调用过多不会导致计数器变成负数,但应避免逻辑错误。
    • 使用带超时的 await 方法可以避免无限等待。

总结

CountDownLatch 是一个简单而强大的同步工具,适用于协调多线程任务的完成顺序或统一启动。通过设置计数器和使用 await/countDown 方法,它能有效地控制线程间的协作。上述示例展示了其在等待任务完成、统一启动并发任务和带超时等待的场景中的应用。

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

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

相关文章

Hadoop之HDFS

Hadoop之HDFS HDFS的Shell操作 启动Hadoop集群(方便后续测试) [atguigu@hadoop102 ~]$ sbin/start-dfs.sh [atguigu@hadoop102 ~]$ sbin/start-yarn.sh-help:输出这个命令参数 [atguigu@hadoop102 ~]$ hadoop fs -help rm-ls:显示目录信息 [atguigu@hadoop102 ~]$ hadoop …

【1.4 漫画PostgreSQL高级数据库及国产数据库对比】

&#x1f418; 漫画PostgreSQL高级数据库及国产数据库对比 &#x1f468;‍&#x1f4bb; 小明&#xff1a;“老王&#xff0c;除了MySQL&#xff0c;还有哪些优秀的关系型数据库&#xff1f;国产数据库发展得怎么样&#xff1f;” &#x1f9d9;‍♂️ 架构师老王&#xff1a;…

OLT、ONU、ONT、SFU、HGU、ODN,它们是什么?它们之间有什么区别?

我们经常会看到OLT、ONU、ONT、SFU、HGU等设备术语。它们分别是什么?又有什么区别呢? PON组件:OLT、ONU、ONT和ODN 无源光网络(PON)采用光纤和分路器&#xff0c;以点对多点拓扑将数据从单一源分发到多个用户。与有源光网络 (AON)不同&#xff0c;PON 仅在光域中运行&#…

sql USING 简化 JOIN 操作

在 SQL 中&#xff0c;USING 是一种用于简化 JOIN 操作的语法糖&#xff0c;它允许你明确指定连接表时所依据的列名。与传统的 ON 子句相比&#xff0c;USING 提供了更简洁的语法1. 基本语法与作用table1 JOIN table2 USING (column_name);将 table1 和 table2 中 column_name …

android开发中的 AndroidX 版本的查看 及 constraintLayout的简单用法

1、查看库的版本 平常我们经常会用到一些库&#xff0c;但是不知道是什么版本&#xff0c;也不知道最新的是什么版本&#xff0c;当然最好的就是到官网去查看&#xff0c;或者三方的maven库。 2、官方地址 AndroidX 版本 | Jetpack | Android Developers 3、比如我们来…

oracle锁表,oracle解锁表,oracle用户连接数

一、查看被锁的表 select sess.sid, sess.serial#, lo.oracle_username, lo.os_user_name, ao.object_name, lo.locked_mode from v$locked_object lo, dba_objects ao, v$session sess where ao.object_id lo.object_id and lo.session_id sess.sid; 二、解锁表语句 alter …

3D可视化:开启多维洞察新时代

3D可视化技术以一种前所未有的方式&#xff0c;将数据、模型与现实世界紧密相连&#xff0c;为人们带来了沉浸式、交互式的全新体验&#xff0c;彻底革新了信息的呈现与理解方式。一、3D可视化的技术原理从技术本质来看&#xff0c;3D可视化基于一系列复杂而精妙的原理。通过数…

List中的对象进行排序处理

以下是使用 Java Stream 对对象列表按 id 和 age 排序的完整示例&#xff0c;包含升序和降序两种场景&#xff1a; 1. 定义测试对象类 Getter Setter public class Person {private int id;private int age; }2. 排序实现代码 import java.util.*; import java.util.stream.…

秋招Day14 - Redis - 底层结构

Redis都有哪些底层数据结构&#xff1f; 有八种核心的底层数据结构。 SDS Redis自己实现的动态字符串&#xff0c;SDS结构中直接存储了已使用的字符数组长度len和未使用的字符数组长度free&#xff0c;所以获取长度的时间复杂度是O(1)&#xff0c;还支持动态扩容&#xff0c…

使用Mac自带的图像捕捉导出 iPhone 相册

用 数据线 将 iPhone 连接到 Mac必须是数据线,有些充电线插上去后无法识别到iphone在 iPhone 上点击“信任此电脑”在 Mac 上打开应用&#xff1a;快速方式&#xff1a;按 Command Space 打开 Spotlight&#xff0c;输入 图像捕捉 或 Image Capture&#xff0c;回车或者从 /系…

【UniApp picker-view 多列对齐问题深度剖析与完美解决】

UniApp picker-view 多列对齐问题深度剖析与完美解决一次看似简单的样式调整&#xff0c;却引发了对构建工具、CSS 预处理和组件渲染机制的深度思考创作时间: 2025/7/1 技术栈: UniApp Vue3 TypeScript PostCSS 问题级别: &#x1f534; 高级&#x1f3af; 问题背景 在开发 …

R Studio开发中记录

1.如何将tar.gz格式的源码R包编译为zip格式的二进制R包。 R CMD INSTALL --build knhanes.tar.gz R CMD INSTALL --build nhanes.tar.gz 2.下载RTools RTools: Toolchains for building R and R packages from source on Windows 3.修改环境变量 PATH$PATH:/d/rtools45/usr…

量化交易中的隐藏模式识别:基于潜在高斯混合模型的机会挖掘

*——从市场噪声中提取黄金信号的数学艺术** > 2025年3月,某对冲基金使用潜在高斯混合模型捕捉到铜期货的异常波动模式,提前布局实现单月收益47%。核心代码仅20行,却颠覆了传统技术分析范式。 --- ### 01 市场迷思:为何90%的交易者失败? 金融市场本质是**非…

Qt窗口被外部(非Qt内部机制)强制销毁,第二次再重复使用不显示

在Qt开发中&#xff0c;窗口被外部&#xff08;非Qt内部机制&#xff09;强制销毁 警告信息 External WM_DESTROY received for QWidgetWindow(0x108b8cbdb10, name"xxxxx") , parent: QWindow(0x0) , transient parent: QWindow(0x0) 使用场景 代码结构如下&#x…

一文详解Character AI:实用指南+ ChatGPT、Gemini对比分析

本指南将深入剖析Character AI的运行机制、功能特性及其存在的局限性。 近年来&#xff0c;生成式人工智能领域发展态势迅猛&#xff0c;其应用范畴已远超单纯的文本生成领域。在众多备受瞩目的新兴平台中&#xff0c;Character AI是一款支持用户以对话形式与人工智能生成角色…

遗传算法的原理与实现示例

遗传算法是一种受生物进化理论启发的随机优化算法&#xff0c;其核心思想是模拟自然界中 “物竞天择、适者生存” 的进化过程&#xff0c;通过对候选解的迭代优化&#xff0c;找到问题的最优解。 一、核心思想 遗传算法将优化问题的候选解视为生物群体中的“个体”&#xff0c…

centos7 ping127.0.0.1不通

ping 127.0.0.1&#xff0c;localhost和本地ip都不通&#xff0c;所有的配置也是正确的 检查下是否禁止了ping vim /proc/sys/net/ipv4/icmp_echo_ignore_all 内容为 1 禁止ping 内容为0 开启ping sysctl -w net.ipv4.icmp_echo_ignore_all0 变更以上设置即可

【无标题】JavaScript入门

JS 1.JS引入方式 <!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>JS-引入方式</title><!-- …

(JAVA)自建应用调用企业微信API接口,实现消息推送

建议先简单了解企业微信开发者中心文档&#xff1a;开发前必读 - 文档 - 企业微信开发者中心 了解一下企业微信调用接口的基础参数&#xff1a;基本概念介绍 - 文档 - 企业微信开发者中心 本篇每个步骤都会跟着官网文档走&#xff0c;都会贴上相关链接&#xff0c;看完本篇文…

P/Invoke 在默认封送(marshalling)规则下,常见托管 ⇄ 非托管类型的对应关系

下表整理了 P/Invoke 在默认封送&#xff08;marshalling&#xff09;规则下&#xff0c;常见托管 ⇄ 非托管类型的对应关系。 内容主要依据微软官方 Marshalling Data with Platform Invoke 文档&#xff0c;并补充了常见指针&#xff0f;句柄用法与字符串缓冲区&#xff…