CountDownLatch深度解析

996 阅读4分钟

一、核心设计思想

1. 计数器机制

  • 初始化:设定一个初始计数值(如 N)。
  • 等待线程:调用 await() 的线程会阻塞,直到计数器归零。
  • 任务线程:每个任务线程完成工作后调用 countDown(),计数器减 1。

2. 一次性特性

  • 不可重置:计数器归零后无法重复使用,若需重复协作,应选择 CyclicBarrier

3. 典型场景

  • 主从协作:主线程等待所有子线程完成初始化。
  • 并行汇总:多个子任务并行执行,主线程等待所有子任务完成后再汇总结果。

二、核心方法与使用示例

1. 核心方法

方法说明
CountDownLatch(int count)构造函数,初始化计数器。
void await()阻塞当前线程,直到计数器归零(支持可中断等待)。
boolean await(long timeout, TimeUnit unit)阻塞当前线程,超时后自动唤醒。
void countDown()计数器减 1(若计数器已为 0,则调用无效)。
long getCount()获取当前计数器值。

2. 示例:主线程等待子线程完成任务

public class MainSubThreadCoordination {
    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(() -> {
                try {
                    // 模拟任务执行
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 完成任务");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown(); // 确保计数器递减
                }
            }).start();
        }

        latch.await(); // 主线程等待所有子线程完成
        System.out.println("所有任务完成,主线程继续执行");
    }
}

三、底层实现:基于 AQS 的共享模式

CountDownLatch 的核心依赖于 AQS(AbstractQueuedSynchronizer)共享模式,通过状态变量 state 表示计数器。

1. AQS 状态管理

  • 初始化state 被设置为初始计数值(如 N)。
  • countDown():调用 releaseShared(1),通过 CAS 将 state 减 1。
  • await():调用 acquireSharedInterruptibly(1),若 state != 0,线程进入 CLH 队列阻塞。

2. 关键源码分析

// CountDownLatch.Sync 内部类(继承 AQS)
private static final class Sync extends AbstractQueuedSynchronizer {
    Sync(int count) {
        setState(count); // 初始化计数器
    }

    // 尝试获取共享锁:当 state == 0 时返回 1,否则返回 -1
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    // 尝试释放共享锁:CAS 递减 state
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0) return false; // 计数器已归零
            int nextc = c - 1;
            if (compareAndSetState(c, nextc))
                return nextc == 0; // 返回 true 表示计数器归零,唤醒等待线程
        }
    }
}

3. await() 的执行流程

  1. 检查 state 是否为 0,若是则直接返回。
  2. state > 0,将当前线程加入 CLH 队列并阻塞。
  3. 当某个线程调用 countDown() 导致 state 归零时,唤醒队列中所有等待线程。

4. countDown() 的执行流程

  1. 通过 CAS 将 state 减 1。
  2. state 归零,调用 AQS.doReleaseShared() 唤醒所有等待线程。

四、使用场景与最佳实践

1. 多线程初始化

// 服务启动时等待所有组件初始化完成
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> { initDB(); latch.countDown(); }).start();
new Thread(() -> { initCache(); latch.countDown(); }).start();
new Thread(() -> { initMQ(); latch.countDown(); }).start();
latch.await(); // 等待所有组件初始化
startServer();

2. 并行任务拆分

// 将大任务拆分为多个子任务并行处理
List<String> data = fetchData(); // 获取数据
CountDownLatch latch = new CountDownLatch(data.size());
for (String item : data) {
    new Thread(() -> {
        process(item); // 处理单个数据项
        latch.countDown();
    }).start();
}
latch.await(); // 等待所有子任务完成
generateReport(); // 生成汇总报告

3. 超时控制

if (latch.await(10, TimeUnit.SECONDS)) {
    System.out.println("所有任务在10秒内完成");
} else {
    System.out.println("部分任务超时未完成");
}

五、常见问题与解决方案

1. 计数器不可重置

  • 问题CountDownLatch 的计数器归零后无法重用。
  • 解决:若需重复使用,改用 CyclicBarrier

2. 死锁风险

  • 场景:子线程未正确调用 countDown()(如异常未执行到 finally 块)。
  • 解决:确保 countDown()finally 块中调用。
    try {
        // 任务代码
    } finally {
        latch.countDown(); // 必须执行
    }
    

3. 线程数不足

  • 错误示例:初始化 CountDownLatch(3),但只有 2 个线程调用 countDown()
  • 解决:确保线程数与计数器值匹配。

六、CountDownLatch vs 其他同步工具

工具核心特性适用场景
CountDownLatch一次性,主线程等待子线程完成。主从协作、并行任务汇总
CyclicBarrier可重置,线程互相等待到达屏障点。并行任务分阶段协同
CompletableFuture支持链式异步任务编排,可组合多个任务。复杂异步流程控制
Phaser动态注册/注销线程,支持多阶段协同。分阶段动态调整线程协作

七、性能优化与注意事项

  1. 合理设置计数器值:避免过大(内存开销)或过小(无法发挥并发优势)。
  2. 避免长时间阻塞:结合 await(timeout, unit) 防止线程永久挂起。
  3. 线程池配合:使用线程池管理任务线程,避免频繁创建/销毁线程。
    ExecutorService executor = Executors.newFixedThreadPool(4);
    CountDownLatch latch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
        executor.submit(() -> {
            try {
                // 任务代码
            } finally {
                latch.countDown();
            }
        });
    }
    latch.await();
    executor.shutdown();
    

总结

CountDownLatch 通过简洁的计数器模型,为多线程协作提供了高效且直观的解决方案。其基于 AQS 的共享模式实现确保了高性能与线程安全性。