首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CountDownLatch倒计时器详解

CountDownLatch倒计时器详解

作者头像
用户11877422
发布2025-11-03 17:38:09
发布2025-11-03 17:38:09
600
举报
运行总次数:0

前言

博主前段时间在公司写一个新需求的时候发现有同事写出了这么一串代码:

看到这块代码,博主傻眼了。看方法名 syncWait猜测应该是跟多线程有关,同步等待的。于是,博主立马去请教和学习了 CountDownLacth这个 JUC 包下的计时器类。下面,和大家一起再回顾下这个方法的使用!

1. 介绍

CountDownLacth是 Java 中位于 JUC(java.util.concurrent)下的一个并发工具类,用于协调多个线程之间的同步。其作用是同步一个或多个线程。强制它们等待由其它线程执行的任务完成。

2. 实现原理

CountDownLacth底层是由 AQS提供支持,来实现线程间的同步。

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

3. 特性

  1. CountDownLatch可以用于控制一个或多个线程等待多个任务完成后再执行。
  2. CountDownLatch计数器只能够被减少,不能够被增加
  3. CountDownLatch的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。

4. 使用场景

  1. 主线程需要等待多个子线程执行完后再执行。例如:一个任务耗时 1min,此时可以用多个线程并发去执行高耗时的操作。等所有子线程都执行完毕后再让主线程继续执行将处理结果进行合并。
  2. 启动多个线程并发执行任务时,主线程需要等待所有线程执行完毕后进行结果汇总。例如:在一个并发请求量比较大的 Web 服务中,可以使用 CountDownLatch 控制多个线程同时处理请求,等待所有线程处理完毕后将结果进行汇总。
  3. 线程 A 等待线程 B 执行完某个任务后再执行自己额任务。例如:在多线程中,一个节点需要等待其他节点的加入后才能执行某个任务,可以使用 CountDownLatch 控制节点的加入,等所有节点都加入完成后再执行任务。
  4. 多个线程同时等待一个共享资源的初始化完成后再进行操作。例如:在某个资源初始化较慢的系统中,可以使用 CountDownLatch 控制多个线程等待共享资源初始化完成后再进行操作。

总结:

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

5. 注意事项

  1. CountDownLatch对象的计数器只能减不能增,即一旦计数器为 0,就无法再重新设置为其他值,因此在使用时需要根据实际需要设置初始值。
  2. CountDownLatch的计数器是线程安全的,多个线程可以同时调用countDown()方法,而不会产生冲突。
  3. 如果CountDownLatch的计数器已经为 0,再次调用countDown()方法也不会产生任何效果。
  4. 如果在等待过程中,有线程发生异常或被中断,计数器的值可能不会减少到 0,因此在使用时需要根据实际情况进行异常处理。确保无论正常结束还是异常关闭都能调用到 countDown()方法来减少计数。
  5. CountDownLatch可以与其他同步工具(如 Semaphore、CyclicBarrier)结合使用,实现更复杂的多线程同步。

6. 实战案例

6.1. 案例一:使用 await()

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

代码语言:javascript
代码运行次数:0
运行
复制
import java.util.concurrent.CountDownLatch;

public class testountDownLatch {

    public static void main(String[] args) throws InterruptedException {
        // 创建 CountDownLatch 对象,需要等待 3 个线程完成任务
        CountDownLatch latch = new CountDownLatch(3);

        // 创建三个子线程
        Worker worker1 = new Worker(latch, "worker1");
        Worker worker2 = new Worker(latch, "worker2");
        Worker worker3 = new Worker(latch, "worker3");

        // 启动三个子线程
        worker1.start();
        worker2.start();
        worker3.start();

        // 主线程等待三个子线程执行完毕
        latch.await();

        // 所有子线程执行完毕后,主线程开始执行
        System.out.println("main 开始继续执行");
    }
}


class Worker extends Thread {

    private final CountDownLatch latch;
    public String name;

    public Worker(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }

    @Override
    public void run() {
        // 假设这是每个子线程要执行的操作
        try {
            Thread.sleep(1000);
            System.out.println("name = " + name);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            latch.countDown();
        }
    }
}

运行结果:

name = worker3

name = worker2

name = worker1

main 开始继续执行

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

思考一:如果这段代码中不使用 CountDownLatch 会怎么样?

运行结果:

main 开始继续执行

name = worker2

name = worker3

name = worker1

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

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

我们模拟 worker1 线程异常,让该线程异常时无法调用 .countDown()方法。

代码语言:javascript
代码运行次数:0
运行
复制
public void run() {
    try {
        // 模拟任务耗时
        if ("worker1".equals(name)) {
            throw new RuntimeException(name + "运行异常");
        }
        Thread.sleep(1000);
        LOGGER.info("{} has finished the job!", name);
        latch.countDown();
    } catch (InterruptedException e) {
        LOGGER.error(e.getMessage(), e);
    }
}

运行结果:

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

6.2. 案例二:使用 await(long timeout, TimeUnit unit)

案例一遗留下来的问题在于如果有一个线程未能成功释放计数器,就会导致所有线程自旋。那么我们的解决方法很明确,就是要让线程在某个时间点后自动的释放。

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

于是,我就请教了公司一位大佬。他告诉我使用 await(long timeout, TimeUnit unit)。我恍然大悟,之前自己学过,但是一到战场上我就把他给忘记了。

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

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

结果如下:

至此,问题全部解决。countDownLatch也已经带大家学习完毕!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1. 介绍
  • 2. 实现原理
  • 3. 特性
  • 4. 使用场景
  • 5. 注意事项
  • 6. 实战案例
    • 6.1. 案例一:使用 await()
    • 6.2. 案例二:使用 await(long timeout, TimeUnit unit)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档