CountDownLatch 类的作用是什么?请解释其工作原理和适用场景。

参考回答

CountDownLatch 是 Java 并发工具类,用来实现 线程之间的同步。它允许一个或多个线程等待,直到其他线程完成一组操作后再继续执行。

作用

  1. 主线程等待多个子线程完成任务
  2. 控制某些操作必须在其他操作完成之后再执行

工作原理

  • CountDownLatch 初始化时需要传入一个计数值 count
  • 线程通过调用 await() 方法进入等待状态,直到计数值减为 0。
  • 每当一个线程完成任务后,调用 countDown() 方法使计数值减 1。
  • 当计数值减为 0 时,所有调用 await() 方法等待的线程被唤醒,继续执行。

适用场景

  1. 任务分解与汇总:主线程等待所有子线程完成后继续执行。
  2. 多服务依赖启动:确保主服务在所有依赖服务启动后开始运行。

详细讲解与拓展

1. CountDownLatch 的基本使用

示例代码

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is working.");
                latch.countDown(); // 每个线程完成后调用 countDown()
            }).start();
        }

        latch.await(); // 主线程等待
        System.out.println("All threads have finished. Main thread resumes.");
    }
}

输出示例

Thread-0 is working.
Thread-1 is working.
Thread-2 is working.
All threads have finished. Main thread resumes.

分析

  1. CountDownLatch 初始值为 3。
  2. 每个子线程完成任务后,调用 countDown(),将计数值减 1。
  3. 主线程调用 await(),直到计数值减为 0,主线程继续执行。

2. CountDownLatch 的工作原理

CountDownLatch 的实现依赖于 AQS(AbstractQueuedSynchronizer)

  1. 计数器维护
    • 内部通过 AQS 的状态(state)保存计数值。
    • countDown():将 state 减 1。
    • await():当 state 为 0 时,唤醒所有等待线程。
  2. 线程等待
    • 调用 await() 的线程会被阻塞并加入 AQS 队列,直到计数器变为 0。

3. 常见场景

场景 1:任务分解与汇总

描述

  • 将一个任务拆分为多个子任务,主线程等待所有子任务完成后再汇总结果。

示例代码

import java.util.concurrent.CountDownLatch;

public class TaskDivisionExample {
    public static void main(String[] args) throws InterruptedException {
        int taskCount = 5;
        CountDownLatch latch = new CountDownLatch(taskCount);

        for (int i = 0; i < taskCount; i++) {
            int taskId = i;
            new Thread(() -> {
                System.out.println("Task " + taskId + " is completed.");
                latch.countDown(); // 子任务完成后减 1
            }).start();
        }

        latch.await(); // 主线程等待所有子任务完成
        System.out.println("All tasks are completed. Proceeding to next step.");
    }
}

场景 2:服务启动顺序控制

描述

  • 确保主服务在所有依赖服务启动后再启动。

示例代码

import java.util.concurrent.CountDownLatch;

public class ServiceStartupExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        new Thread(() -> {
            System.out.println("Service A started.");
            latch.countDown();
        }).start();

        new Thread(() -> {
            System.out.println("Service B started.");
            latch.countDown();
        }).start();

        new Thread(() -> {
            System.out.println("Service C started.");
            latch.countDown();
        }).start();

        latch.await(); // 等待所有服务启动
        System.out.println("Main service started.");
    }
}

输出示例

Service A started.
Service B started.
Service C started.
Main service started.

场景 3:模拟并发测试

描述

  • 模拟多个线程同时发起请求。

示例代码

import java.util.concurrent.CountDownLatch;

public class ConcurrentTestExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 5;
        CountDownLatch readyLatch = new CountDownLatch(threadCount);
        CountDownLatch startLatch = new CountDownLatch(1);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is ready.");
                readyLatch.countDown();
                try {
                    startLatch.await(); // 等待统一开始信号
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " started.");
            }).start();
        }

        readyLatch.await(); // 等待所有线程准备好
        System.out.println("All threads are ready. Start signal sent.");
        startLatch.countDown(); // 发送开始信号
    }
}

输出示例

Thread-0 is ready.
Thread-1 is ready.
Thread-2 is ready.
Thread-3 is ready.
Thread-4 is ready.
All threads are ready. Start signal sent.
Thread-0 started.
Thread-1 started.
Thread-2 started.
Thread-3 started.
Thread-4 started.

4. 注意事项

  1. 一次性使用
    • CountDownLatch 不能重置或复用,若需要复用,考虑使用 CyclicBarrier 或其他工具类。
  2. 线程安全性
    • countDown() 是线程安全的,可以被多个线程并发调用。
  3. 计数值设计
    • 初始化时计数值应与任务数量一致,否则可能导致线程永久等待。

总结

特性 描述
作用 让一个线程等待多个线程完成操作;或控制线程启动顺序。
工作原理 使用 AQS 维护计数器状态,计数器减为 0 时,释放所有等待线程。
适用场景 任务分解与汇总、服务启动顺序控制、并发测试等。
优点 简化线程之间的同步控制,使用简单,线程安全。
注意事项 一次性使用,不能重置或复用;计数值需与任务数量一致。

发表评论

后才能评论