面试题:什么是 Java 的 CountDownLatch?

Java 中的 CountDownLatch(倒计数锁存器)是 java.util.concurrent 包提供的一个同步工具类,用于协调多个线程之间的执行顺序。它的核心功能是允许一个或多个线程等待一组操作完成后再继续执行。以下是详细解析:


1. 核心概念

  • 计数器机制
    CountDownLatch 内部维护一个正数计数器(初始值由用户指定)。
  • 每次调用 countDown() 方法,计数器减 1。
  • 每个调用 await() 的线程会阻塞,直到计数器减为 0,此时所有等待线程被唤醒继续执行。
  • 一次性工具
    CountDownLatch 的计数器只能初始化一次,且无法重置。计数器归零后,工具不再可用。

2. 工作原理

CountDownLatch 的底层实现基于 AQS(AbstractQueuedSynchronizer),其核心逻辑如下:

  1. 初始化
   CountDownLatch latch = new CountDownLatch(n);
  • n 是计数器的初始值(表示需要等待的线程数或任务数)。
  1. 等待线程
    调用 await() 的线程会进入 AQS 的等待队列,阻塞直到计数器为 0:
   latch.await(); // 阻塞当前线程
  1. 减少计数器
    每个工作线程完成任务后调用 countDown(),将计数器减 1:
   latch.countDown(); // 计数器减 1
  1. 唤醒线程
    当计数器减到 0 时,所有等待的线程被唤醒并继续执行后续操作。

3. 主要方法

方法名描述
CountDownLatch(int count)构造函数,初始化计数器为 count(必须 ≥ 0)。
void await()当前线程阻塞,直到计数器归零。
boolean await(long timeout, TimeUnit unit)带超时的等待,若超时未归零则返回 false
void countDown()将计数器减 1。若减到 0,唤醒所有等待线程。
long getCount()返回当前计数器的值(调试时常用)。

4. 使用场景

CountDownLatch 适用于以下典型场景:

  1. 主线程等待子线程完成
    例如,主线程启动多个子线程执行任务,等待所有子线程结束后再继续执行。
   CountDownLatch latch = new CountDownLatch(3);
   for (int i = 0; i < 3; i++) {
       new Thread(() -> {
           // 执行任务
           latch.countDown();
       }).start();
   }
   latch.await(); // 主线程等待
   System.out.println("所有子线程已完成");
  1. 并行计算
    将大任务拆分为多个子任务,等待所有子任务完成后合并结果。
  2. 测试并发程序
    模拟多个线程同时触发某个操作的场景(如压力测试)。
  3. 资源初始化
    确保所有资源加载完成后再启动主服务。

5. 示例代码

场景:游戏等待所有玩家准备就绪

import java.util.concurrent.CountDownLatch;

class Player implements Runnable {
    private final CountDownLatch latch;
    private final String name;

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

    @Override
    public void run() {
        try {
            Thread.sleep((long) (Math.random() * 3000)); // 模拟准备时间
            System.out.println(name + " 已准备好");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown(); // 玩家准备就绪
        }
    }
}

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

        for (int i = 1; i <= playerCount; i++) {
            new Thread(new Player(latch, "玩家" + i)).start();
        }

        latch.await(); // 等待所有玩家就绪
        System.out.println("所有玩家已准备好,游戏开始!");
    }
}

6. CountDownLatch vs 其他工具

工具特点
CountDownLatch计数器只能递减,且不可重置。适合一次性等待多个线程完成。
CyclicBarrier计数器可重置,适合多阶段任务(如多次并行计算)。
Semaphore控制同时访问的线程数量(资源池管理)。
CompletableFuture支持异步任务链式调用和组合操作(如任务完成后的回调)。

7. 注意事项

  1. 避免死锁
    确保所有工作线程都能正确调用 countDown(),否则计数器可能永远无法归零,导致等待线程永久阻塞。
  2. 中断处理
    await() 方法会响应中断,需合理处理 InterruptedException
  3. 性能优化
    CountDownLatch 基于 AQS 实现,其性能在并发场景下表现良好,但需避免在高竞争场景中频繁创建实例。

总结

CountDownLatch 是 Java 并发编程中的核心工具之一,通过计数器机制协调线程间的执行顺序。它适用于需要等待一组操作完成后再继续执行的场景(如多线程任务协作、资源初始化等)。理解其底层原理(AQS)和使用场景,有助于编写高效、可靠的并发程序。

THE END
喜欢就支持一下吧
点赞8 分享