文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

CountDownLatch介绍和使用【Java多线程必备】

2023-10-21 11:06

关注

点击 Mr.绵羊的知识星球 解锁更多优质文章。

目录

一、介绍

二、特性

三、实现原理

四、适用场景

五、注意事项

六、实际应用


一、介绍

    CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。它可以使一个或多个线程等待一组事件的发生,而其他的线程则可以触发这组事件。

二、特性

CountDownLatch 可以用于控制一个或多个线程等待多个任务完成后再执行。

CountDownLatch 的计数器只能够被减少,不能够被增加。

CountDownLatch 的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。

三、实现原理

    CountDownLatch 的实现原理比较简单,它主要依赖于 AQS(AbstractQueuedSynchronizer)框架来实现线程的同步。

    CountDownLatch 内部维护了一个计数器,该计数器初始值为 N,代表需要等待的线程数目,当一个线程完成了需要等待的任务后,就会调用 countDown() 方法将计数器减 1,当计数器的值为 0 时,等待的线程就会开始执行。

四、适用场景

主线程等待多个子线程完成任务后再继续执行。例如:一个大型的任务需要被拆分成多个子任务并交由多个线程并行处理,等所有子任务都完成后再将处理结果进行合并。

启动多个线程并发执行任务,等待所有线程执行完毕后进行结果汇总。例如:在一个并发请求量比较大的 Web 服务中,可以使用 CountDownLatch 控制多个线程同时处理请求,等待所有线程处理完毕后将结果进行汇总。

线程 A 等待线程 B 执行完某个任务后再执行自己的任务。例如:在分布式系统中,一个节点需要等待其他节点的加入后才能执行某个任务,可以使用 CountDownLatch 控制节点的加入,等所有节点都加入完成后再执行任务。

多个线程等待一个共享资源的初始化完成后再进行操作。例如:在某个资源初始化较慢的系统中,可以使用 CountDownLatch 控制多个线程等待共享资源初始化完成后再进行操作。

CountDownLatch 适用于多线程任务的协同处理场景,能够有效提升多线程任务的执行效率,同时也能够降低多线程任务的复杂度和出错率。

五、注意事项

CountDownLatch 对象的计数器只能减不能增,即一旦计数器为 0,就无法再重新设置为其他值,因此在使用时需要根据实际需要设置初始值。

CountDownLatch 的计数器是线程安全的,多个线程可以同时调用 countDown() 方法,而不会产生冲突。

如果 CountDownLatch 的计数器已经为 0,再次调用 countDown() 方法也不会产生任何效果。

如果在等待过程中,有线程发生异常或被中断,计数器的值可能不会减少到 0,因此在使用时需要根据实际情况进行异常处理。

CountDownLatch 可以与其他同步工具(如 Semaphore、CyclicBarrier)结合使用,实现更复杂的多线程同步。

六、实际应用

1. 案例一

    (1) 场景

    一个简单的 CountDownLatch 示例,演示了如何使用 CountDownLatch 实现多个线程的同步。

    (2) 代码

import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.concurrent.CountDownLatch;import java.util.concurrent.TimeUnit;public class CountDownLatchCase1 {    private static final Logger LOGGER = LoggerFactory.getLogger(CountDownLatchCase1.class);    public static void main(String[] args) throws InterruptedException {        // 创建 CountDownLatch 对象,需要等待 3 个线程完成任务        CountDownLatch latch = new CountDownLatch(3);        // 创建 3 个线程        Worker worker1 = new Worker(latch, "worker1");        Worker worker2 = new Worker(latch, "worker2");        Worker worker3 = new Worker(latch, "worker3");        // 启动 3 个线程        worker1.start();        worker2.start();        worker3.start();        // 等待 3 个线程完成任务        latch.await();        // 所有线程完成任务后,执行下面的代码        LOGGER.info("All workers have finished their jobs!");    }}class Worker extends Thread {    private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class);    private final CountDownLatch latch;    public String name;    public Worker(CountDownLatch latch, String name) {        this.latch = latch;        this.name = name;    }    @Override    public void run() {        try {            // 模拟任务耗时            TimeUnit.MILLISECONDS.sleep(1000);            LOGGER.info("{} has finished the job!", name);        } catch (InterruptedException e) {            LOGGER.error(e.getMessage(), e);        } finally {            // 一定要保证每个线程执行完毕或者异常后调用countDown()方法            // 如果不调用会导致其他线程一直等待, 无法继续执行            // 建议放在finally代码块中, 防止异常情况下未调用countDown()方法            latch.countDown();        }    }}

运行结果:

    在上面的代码中,首先创建了一个CountDownLatch对象,并指定需要等待的线程数为 3。然后创建了 3 个线程并启动。每个线程会模拟执行一个耗时的任务,执行完成后会调用 countDown() 方法将计数器减 1。在所有线程都完成任务后,主线程会执行 latch.await() 方法等待计数器为 0,然后输出所有线程都完成任务的提示信息。

    思考:如果不使用CountDownLatch情况将会是怎样呢?

    运行结果:

    由执行结果可知,主线程不会等待子线程结束后再执行。如果我们主线程(main) 需要其他线程执行后的结果,我们就需要使用countDownLantch让主线程和执行快的线程等待子线程全部执行完毕再向下执行。

    思考:如果某个线程漏调用.countDown();会怎么样呢?

    接下来我们模拟worker1线程异常,如果该线程异常latch.countDown()方法就无法被调用。

public void run() {    try {        // 模拟任务耗时        if ("worker1".equals(name)) {            throw new RuntimeException(name + "运行异常");        }        TimeUnit.MILLISECONDS.sleep(1000);        LOGGER.info("{} has finished the job!", name);        latch.countDown();    } catch (InterruptedException e) {        LOGGER.error(e.getMessage(), e);    }}

    运行结果:

    由运行结果可知,当worker1线程由于异常没有执行countDown()方法,最后state结果不为0,导致所有线程停在AQS中自旋(死循环)。所以程序无法结束。(如何解决这个问题呢?请看案例二)

2. 案例二

    (1) 场景

    当年刚工作不久,遇到一个这样的问题:远程调用某个api,大部分情况下需要2-3s才能读取到响应值。我需要解析响应的JSON用于后续的操作。由于这个调用是异步的,我没办法在主线程获取到响应的JSON值。

    当时第一时间想到的是让主线程休眠,但是休眠多久好呢?1、2、3s?显然是不行的,如果1s就请求成功并响应了,你要等3s,这不是浪费时间吗!

    于是,我就请教了公司一位大佬。他告诉我使用CountDownLatch。我恍然大悟,之前自己学过,但是一到战场上我就把他给忘记了(实践是检验真理的唯一标准)。

(2) 代码(偷个懒 哈哈 就是在案例一的代码中修改了await()方法)

    将latch.await()修改为 latch.await(5, TimeUnit.SECONDS),这段代码啥意思呢?就是当第一个线程到达await()方法开始计时,5s后不等待未执行完毕的线程,直接向下执行。这么写的好处是,当调用某个方法超时太久,不影响我们的主逻辑。(很实用)

// 等待 3 个线程完成任务if (!latch.await(5, TimeUnit.SECONDS)) {    LOGGER.warn("{} time out", worker1.name);}// 所有线程完成任务后,执行下面的代码LOGGER.info("all workers have finished their jobs!");

    看一下加了latch.await(5, TimeUnit.SECONDS)方法后执行结果:

来源地址:https://blog.csdn.net/qq_45871274/article/details/130223673

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯