CountDownLatch 是 Java 并发工具类(位于 java.util.concurrent 包),核心作用是 让一个或多个线程等待其他线程完成一系列操作后,再继续执行。它基于「计数器」实现,本质是一种「同步屏障」。
一、核心概念
- 计数器初始化:创建
CountDownLatch时,指定一个非负整数作为初始计数(代表需要等待的「任务数」或「线程数」)。 - 计数减少:其他线程完成任务后,调用
countDown()方法,计数器会 原子性递减 1(线程安全,无需额外加锁)。 - 线程等待:需要等待的线程(通常是主线程)调用
await()方法,会 阻塞当前线程,直到计数器变为 0。 - 超时等待:可选调用
await(long timeout, TimeUnit unit),若超时后计数器仍未为 0,线程会自动唤醒并返回false。
二、核心特点
- 一次性使用:计数器递减到 0 后,
CountDownLatch就失效了,无法再次重置计数(若需重复使用,可考虑CyclicBarrier)。 - 非阻塞递减:
countDown()方法不会阻塞调用线程,调用后立即返回,仅减少计数。 - 多线程共享:多个线程可同时调用
countDown()或await(),计数器状态对所有线程可见。
三、适用场景
- 等待多线程完成任务:比如主线程等待多个子线程下载文件、处理数据后,再合并结果。
- 异步转同步:比如本文开头的「短信发送异步回调转同步」—— 主线程发起短信请求后阻塞,直到回调线程调用
countDown()唤醒。 - 协调线程执行顺序:比如让多个线程同时开始执行(先让所有线程等待,计数器归 0 后同时唤醒)。
四、代码示例(基础用法)
场景:主线程等待 3 个子线程完成任务后,再执行后续逻辑
java
运行
1 | import java.util.concurrent.CountDownLatch; |
输出结果:
plaintext
1 | 主线程等待所有子线程完成任务... |
五、关键注意事项
countDown()必须执行:若子线程任务异常终止,未调用
countDown(),计数器会一直不为 0,主线程会永久阻塞(或超时后返回)。因此,countDown()需放在finally块中,确保无论任务成功 / 失败都执行。避免计数器初始化为 0:
若初始计数为 0,
await()会立即返回,countDown()会让计数变为负数(无实际意义),失去同步作用。超时时间合理设置:
若业务允许任务超时,建议使用
await(long, TimeUnit),避免主线程无限阻塞导致系统资源占用。与
CyclicBarrier的区别:CountDownLatch:计数器一次性递减,适合「等待多线程完成任务」。CyclicBarrier:计数器可重复重置,适合「多个线程互相等待,到达屏障后再一起继续执行」(比如多阶段任务协作)。
六、在「短信异步转同步」中的应用(回顾)
在本文开头的短信场景中:
- 主线程发起短信请求前,创建
CountDownLatch(1)(只需等待 1 个回调事件)。 - 主线程调用
await(30, TimeUnit.SECONDS)阻塞,等待回调。 - 短信平台回调接口被触发后,调用
countDown(),计数器变为 0,主线程被唤醒,读取短信发送结果。 - 若 30 秒内未收到回调,
await()返回false,主线程判定为「超时」。
这种方式确保了主线程能同步获取异步回调的结果,同时避免了无限阻塞。