CountDownLatch详解以及用法示例

news2024/11/15 15:44:11

一、什么是CountDownLatch

CountDownLatchcount down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉

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

总结来说,CountDownLatch的作用就是等待其他的线程都执行完任务,必要时可以对各个任务的执行结果进行汇总,然后主线程才继续往下执行。

二、主要方法

  1. CountDownLatch(int count):构造方法,创建一个新的 CountDownLatch 实例,用给定的计数初始化。参数 count 表示线程需要等待的任务数量。
int numberOfTasks = 5;
CountDownLatch latch = new CountDownLatch(numberOfTasks);
  1. void await():使当前线程等待,直到计数器值变为0,除非线程被 interrupted。如果计数器的值已经为0,则此方法立即返回。在实际应用中,通常在主线程中调用此方法,等待其他子线程完成任务。
latch.await();
  1. boolean await(long timeout, TimeUnit unit):使当前线程等待,直到计数器值变为0,或者指定的等待时间已到,或者线程被 interrupted。如果计数器的值已经为0,则此方法立即返回。
    参数 timeout 是指定的等待时间,
    参数 unittimeout 的单位(如秒、毫秒等)。
    此方法返回一个布尔值,表示在等待时间内计数器是否变为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.Futurejava.util.concurrent.ExecutorService

四、使用场景

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

四、示例代码

4.1 示例代码

下面的示例展示了一个简单的网站爬虫,它使用 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;
        }

        @Override
        public 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("所有爬虫任务都已完成!");
    }
}

4.2 运行结果

在这里插入图片描述

开始爬虫任务...
爬取 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
所有爬虫任务都已完成!

五、稍复杂点的示例代码

5.1 代码示例讲解

这是一个稍复杂的 CountDownLatch 示例。

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

  • 其中有一个倒计时开始。
  • 一旦倒计时结束,赛车就开始比赛,
  • 当所有赛车完成比赛时,主线程打印一条消息。
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++) {
      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;
    }

    @Override
    public 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 时,主线程将打印一条消息表示所有赛车都完成了比赛。

5.2 运行结果


倒计时开始...
倒计时: 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.coloradmin.cn/o/594276.html

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

相关文章

订单管理轻松上手:低代码平台助力企业数字化转型

随着电子商务的发展和普及&#xff0c;企业的订单管理工作变得越来越复杂&#xff0c;传统的手工处理方式已经无法满足企业的需求&#xff0c;需要一种更高效、更精确、更可靠的订单管理方式。而低代码技术正是满足这一需求的一种有效解决方案。 一、订单管理的重要性 企业的…

高频面试八股文原理篇(三)Array,Vector,ArrayList和LinkedList区别

目录 数组(Array)和列表(ArrayList)的区别 Arrays工具类 Vector,ArrayList, LinkedList的区别 ArrayList扩容机制&#xff08;原理) arrayList为什么线程不安全&#xff1f; 数组(Array)和列表(ArrayList)的区别 Array可以包含基本类型和对象类型&#xff0c;ArrayList只能…

电脑重装系统后,打印机驱动没了怎么办

驱动人生获悉&#xff0c;据微软最新消息&#xff0c;23年6月将对Windows 10进行强制升级&#xff0c;自动升级到22H2版本。不少用户听闻后&#xff0c;开始纷纷将系统重装到了Windows 11等&#xff0c;不过升级也以为着很多系统的东西需要推倒重来&#xff0c;其中就包括打印机…

Jupyter Notebook如何导入导出文件

目录 0.系统&#xff1a;windows 1.打开 Jupyter Notebook 2.Jupyter Notebook导入文件 3.Jupyter Notebook导出文件 0.系统&#xff1a;windows 1.打开 Jupyter Notebook 1&#xff09;下载【Anaconda】后&#xff0c;直接点击【Jupyter Notebook】即可在网页打开 Jupyte…

【C++中map和unordered_map存储自定义类型需要做什么】

目录 一、map存储自定义类型 二、unordered_map存储自定义类型 一、map存储自定义类型 需要传入的参数是key-value键值对&#xff0c;和仿函数类型 对于内置类型&#xff0c;int、double、char重载了operator<所以传入less仿函数不会出错 但是对于自定义类型&#xff0c;如…

MySQL 视图 浅入浅出

前提 最近公司接了一个项目&#xff0c;项目是将一份内容丰富且包含大量数据透视表&#xff08;之所以称为数据透视表&#xff0c;是因为可以动态地改变它们的版面布置&#xff0c;以便按照不同方式分析数据&#xff0c;也可以重新安排行号、列标和页字段。每一次改变版面布置…

搭建一个AI对话机器人——前端ChatUI使用纪录

最近在使用 OpenAI 的 gpt api 搞着玩玩&#xff0c;然后就遇上了前端对话交互实现的需求场景&#xff0c;如何快速实现 CUI&#xff08;Chat User Interface&#xff09;成了问题。最后选择了来自阿里达摩院的ChatUI&#xff0c;本人便用于整理其使用经验。 介绍 服务于对话领…

自动驾驶 | 使用机器学习方法的GNSS多径检测

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 论文标题&#xff1a;GNSS Multipath Detection Using a Machine Learning Approach 论文链接&#xff1a;https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber8317700 摘要 在高度城市化地区…

Yolov8轻量化:EMO,结合 CNN 和 Transformer 的现代倒残差移动模块设计,性能优于EdgeViT、Mobile-former等网络

论文: https://arxiv.org/pdf/2301.01146.pdf 🏆🏆🏆🏆🏆🏆Yolo轻量化模型🏆🏆🏆🏆🏆🏆 重新思考了 MobileNetv2 中高效的倒残差模块 Inverted Residual Block 和 ViT 中的有效 Transformer 的本质统一,归纳抽象了 MetaMobile Block 的一般…

JAVA基础JVM讲解

JVM体系结构 主要包括两个子系统和两个组件&#xff1a; Class loader(类装载器) 子系统&#xff08;用来装载.class文件&#xff09;; Execution engine(执行引擎) 子系统&#xff08;执行字节码&#xff0c;或者执行本地方法&#xff09;&#xff1b; Runtime data area (…

2023年6月18日DAMA-CDGA/CDGP数据治理认证报名指南

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

弹性及其应用

弹性 衡量了买者和卖者对市场环境变化作出反应的程度大小是我们能够更精确的分析供给和需求 需求的价格弹性 给定百分之一的价格变化&#xff0c;需求量变量的百分数就是需求的价格弹性它衡量了一种物品的需求量对该物品价格变化作出反应的程度大小 需求价格弹性的决定因素…

手把手教你搭建OpenStack云平台(超级详细)

一、前言 OpenStack云平台搭建需要两个节点&#xff0c;一个是controller&#xff08;控制节点&#xff09;&#xff0c;另一个是compute&#xff08;计算节点&#xff09;。 控制节点&#xff08;controller&#xff09;规划如下&#xff1a; 一块200G的硬盘。两块网卡&#x…

天地图下载数据的方法

天地图下载数据的方法&#xff0c;目前我知道的&#xff0c;部分数据可以下载&#xff0c;部分数据不提供下载&#xff08;但是有数据&#xff09;具体就不知道原因了&#xff0c;话不多说&#xff1a; 登录全国地理信息资源目录服务系统&#xff08;全国地理信息资源目录服务…

cuda编程

https://docs.nvidia.com/cuda/index.html 这里写目录标题 OpenCL 与OpenGlrelease noteCUDA Features Archivensight visual studio安装指导本地下载和网络下载 bili 视频1c2050 版本Fermi架构3接口延迟cpu逻辑控制&#xff0c;管理 GPU计算核心连接 GPU硬件架构综述架构2atom…

Revit中如何使创建的族文件内存变小

族文件的大小直接影响到项目文件的大小和软件运行速度&#xff0c;如何将族文件做的最小并且满足项目需求呢? 方法一&#xff1a;清除未使用项 1. 族制作完成可以把族文件中未用到的外部载入族或其他多余数据删掉&#xff0c;点击“管理”选项卡下拉的“清除未使用项”命令; 2…

我拒绝了字节的offer,给求职者提个醒....

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是2年。我想说的是&#xff0c;但凡有点机会&#xff0c;千万别去外包&#xff01; 在深思熟虑过后&am…

LabVIEWCompactRIO 开发指南第七章45 将I/O添加到Compact RIO

LabVIEWCompactRIO 开发指南第七章45 将I/O添加到Compact RIO 基于LabVIEW可重配置I/O&#xff08;RIO&#xff09;架构的NI产品日益被采用用于需要高通道数、密集处理和分布式I/O的系统级应用。将RIO扩展I/O添加到NI RIO产品产品中&#xff0c;可实现1&#xff1a;N系统拓扑…

源码分析 | MySQL 的 commit 是怎么 commit 的?

作者&#xff1a;李鹏博 爱可生 DBA 团队成员&#xff0c;主要负责 MySQL 故障处理和 SQL 审核优化。对技术执着&#xff0c;为客户负责。 本文来源&#xff1a;原创投稿 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 …

JavaEE进阶(Mybatis)5/30

目录 1.单元测试 2.生成单元测试 3.Mybatis 插入操作 4.#{}和${}的区别 1.单元测试 优点&#xff0c;1.可以检查方法的正确性 2.可以&#xff0c;在打包前检查程序的错误&#xff0c;减少错误 3.不污染数据库 2.生成单元测试 1.要生成的类内部generate 2.添加单元测试的代码…