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

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

目录

一、介绍

二、特性

三、实现原理

四、适用场景

五、注意事项

六、实际应用


一、介绍

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

二、特性

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

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

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

三、实现原理

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

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

四、适用场景

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

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

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

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

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

五、注意事项

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

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

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

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

5. 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;

/**
 * CountDownLatchCase1
 * 如何使用CountDownLatch实现多个线程的同步。
 *
 * @author wxy
 * @since 2023-04-18
 */
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)方法后执行结果:

  • 26
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
Java 中,CountDownLatch 是一种同步工具,它可以用来控制线程的执行顺序或者等待其他线程完成某些操作。下面是一个使用 CountDownLatch 的示例,用于实现多线程的协调: ```java import java.util.concurrent.CountDownLatch; public class MultiThreadExample { public static void main(String[] args) throws InterruptedException { int threadCount = 5; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { Thread thread = new Thread(new Worker(latch)); thread.start(); } System.out.println("Main thread is waiting for workers to finish."); latch.await(); System.out.println("All workers have finished. Main thread can continue."); } static class Worker implements Runnable { private final CountDownLatch latch; public Worker(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try { System.out.println("Worker is performing some task."); Thread.sleep(1000); System.out.println("Worker has finished its task."); } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); } } } } ``` 在上面的示例中,我们创建了一个 CountDownLatch 对象,并将其初始化为预期的线程数量(5)。然后,在主线程中创建了 5 个 Worker 线程,并启动它们。每个 Worker 线程都会执行一些任务,然后调用 `latch.countDown()` 来通知 CountDownLatch 已经完成了一部分工作。最后,主线程调用 `latch.await()` 来等待所有 Worker 线程完成任务。 当所有 Worker 线程完成任务后,主线程就会继续执行。这样,我们就通过 CountDownLatch 来协调多线程的执行顺序了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

學陽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值