CountDownLatch是Java并发包(java.util.concurrent)中一个非常实用的同步辅助类。我第一次接触这个工具是在处理一个分布式任务调度系统时,需要等待多个子任务完成才能执行主任务。当时用简单的Thread.join()方式实现起来非常笨拙,直到发现了CountDownLatch这个"计数器门闩"。
简单来说,CountDownLatch允许一个或多个线程等待其他线程完成操作。它的核心是一个计数器,初始化时设定计数值,每当一个线程完成自己的任务后,计数器就减1。当计数器值到达0时,表示所有需要等待的操作都已完成,此时在CountDownLatch上等待的线程就可以恢复执行。
注意:CountDownLatch的计数器无法重置,这点与CyclicBarrier不同。如果需要重复使用的场景,应该考虑使用CyclicBarrier。
CountDownLatch的实现依赖于AbstractQueuedSynchronizer(AQS)框架。AQS是Java并发包中许多同步器的基础框架,它通过一个int成员变量表示同步状态,并通过内置的FIFO队列来管理获取同步状态失败的线程。
在CountDownLatch中,这个int状态变量就是计数器。构造函数中传入的count参数实际上就是设置了这个状态变量的初始值:
java复制public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch内部定义了一个继承自AQS的Sync静态内部类。这个类重写了AQS的几个关键方法:
java复制private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// 自旋CAS操作减少计数
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
tryAcquireShared方法决定了线程能否获取锁(即计数器是否已归零),tryReleaseShared则实现了计数器的递减操作。
CountDownLatch的核心API非常简单,只有几个关键方法:
构造函数:CountDownLatch(int count)
await():
await(long timeout, TimeUnit unit):
countDown():
getCount():
最常见的场景是主线程需要等待多个工作线程完成初始化或其他准备工作:
java复制// 创建计数器,初始值为工作线程数
CountDownLatch startLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
// 准备工作
startLatch.countDown();
}).start();
}
// 主线程等待所有工作线程准备就绪
startLatch.await();
// 所有线程就绪后继续执行
当需要并行处理任务然后聚合结果时:
java复制List<Result> results = Collections.synchronizedList(new ArrayList<>());
CountDownLatch doneLatch = new CountDownLatch(TASK_COUNT);
for (Task task : tasks) {
executor.execute(() -> {
try {
results.add(processTask(task));
} finally {
doneLatch.countDown();
}
});
}
// 等待所有任务完成
doneLatch.await();
// 处理最终结果
processResults(results);
在微服务架构中,服务启动时可能需要等待其他服务就绪:
java复制// 服务A启动类
public class ServiceA {
private CountDownLatch dependencyLatch;
public void setDependencyLatch(CountDownLatch latch) {
this.dependencyLatch = latch;
}
public void start() {
new Thread(() -> {
try {
dependencyLatch.await();
// 依赖服务就绪后启动逻辑
startInternal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
在实际项目中,CountDownLatch经常与线程池一起使用。这里有个性能优化的关键点:
java复制ExecutorService executor = Executors.newFixedThreadPool(POOL_SIZE);
CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
executor.submit(() -> {
try {
// 执行任务
} finally {
latch.countDown();
}
});
}
// 等待所有任务完成
latch.await();
executor.shutdown();
重要提示:务必在finally块中调用countDown(),确保即使任务抛出异常计数器也能递减。否则可能导致主线程永久阻塞。
对于复杂的分阶段任务,可以使用多个CountDownLatch形成"阶段屏障":
java复制// 初始化阶段
CountDownLatch initLatch = new CountDownLatch(WORKER_COUNT);
// 处理阶段
CountDownLatch processLatch = new CountDownLatch(WORKER_COUNT);
// 完成阶段
CountDownLatch finishLatch = new CountDownLatch(WORKER_COUNT);
for (int i = 0; i < WORKER_COUNT; i++) {
new Thread(() -> {
// 初始化工作
initLatch.countDown();
try {
initLatch.await(); // 等待所有线程初始化完成
// 处理阶段工作
processLatch.countDown();
processLatch.await(); // 等待所有线程处理完成
// 收尾工作
} finally {
finishLatch.countDown();
}
}).start();
}
finishLatch.await(); // 等待所有线程完全结束
虽然CountDownLatch非常轻量级,但在超高并发场景下仍需注意:
计数器争用:当大量线程同时调用countDown()时,CAS操作可能成为瓶颈
等待线程唤醒:当计数器归零时,所有等待线程会被同时唤醒,可能造成瞬间负载高峰
内存可见性:countDown()操作保证了happens-before关系,确保计数变化对所有线程可见
问题现象:主线程永久阻塞在await()调用上
可能原因:
调试方法:
java复制// 添加超时机制
if (!latch.await(10, TimeUnit.SECONDS)) {
System.err.println("Timeout! Remaining count: " + latch.getCount());
// 获取未完成的任务信息
}
问题现象:计数器变为负数
可能原因:
解决方案:
java复制// 安全计数方法
public void safeCountDown(CountDownLatch latch) {
if (latch.getCount() > 0) {
latch.countDown();
}
}
java复制// 健壮的使用模板
CountDownLatch latch = new CountDownLatch(N);
try {
for (int i = 0; i < N; i++) {
executor.execute(() -> {
try {
// 执行任务
} catch (Exception e) {
// 记录异常但不影响计数
} finally {
latch.countDown();
}
});
}
if (!latch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
handleTimeout();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
handleInterruption();
}
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 重用性 | 不可重用 | 可重用 |
| 计数方向 | 递减 | 递增 |
| 主要用途 | 等待事件 | 线程相互等待 |
| 自动重置 | 否 | 是 |
| 异常处理 | 不影响其他线程 | 会传播给所有线程 |
虽然Semaphore也可以用于类似场景,但语义不同:
在现代Java中,CompletableFuture可以替代部分CountDownLatch的使用场景:
java复制// 使用CompletableFuture实现类似功能
CompletableFuture[] futures = tasks.stream()
.map(task -> CompletableFuture.runAsync(task, executor))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
选择依据:
我在一个电商促销系统中使用CountDownLatch实现了如下场景:
需求:活动开始时,需要:
实现方案:
java复制public class ActivityStarter {
private final CountDownLatch prepareLatch = new CountDownLatch(4);
public void startActivity() {
// 并行执行准备工作
CompletableFuture.runAsync(this::loadInventory)
.thenRun(prepareLatch::countDown);
CompletableFuture.runAsync(this::initCoupons)
.thenRun(prepareLatch::countDown);
CompletableFuture.runAsync(this::warmUpCache)
.thenRun(prepareLatch::countDown);
CompletableFuture.runAsync(this::startMonitoring)
.thenRun(prepareLatch::countDown);
try {
prepareLatch.await(30, TimeUnit.SECONDS);
openActivity();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
handleStartupFailure();
}
}
// 其他方法省略...
}
优化点:
这个实现成功支撑了多次大促活动,系统启动时间从原来的串行执行15秒降低到并行执行的4秒内。