顾名思义,CountDownLatch
是一个用来倒计数的咚咚。如果某项任务可以拆分成若干个子任务同时进行,然后等待所有的子任务完成,可以考虑使用它。
该类的用法非常简单。首先构造一个 CountDownLatch
,唯一的参数是任务数量,一旦构造完毕就不能修改。接着启动所有的子任务(线程),且每个子任务在完成自己的计算后,调用 CountDownLatch#countDown
方法将倒计数减一。最后在主线程中调用 CountDownLatch#await
方法等待计数器归零。
例如赛跑的准备阶段,八名运动员先后到达起点做好准备,然后裁判打响发令枪,准备工作就结束了,比赛开始。如果把从运动员就位到发令枪响看做赛跑准备任务,那么每个运动员的准备过程就是其子任务,可以用 CountDownLatch
模拟如下:
final int count = 8;
System.out.println("运动员开始就位。");
// 构造 CountDownLatch。
final CountDownLatch cdl = new CountDownLatch(count);
for (int i = 1; i <= count; i++) {
final int number = i;
new Thread() {
@Override
public void run() {
System.out.println(number + " 号运动员到场并开始准备...");
try {
// 让运动员随机准备 2~5 秒钟。
TimeUnit.SECONDS.sleep(new Random().nextInt(4) + 2);
} catch (InterruptedException ex) {
}
System.out.println(number + " 号运动员就位。");
// 倒计数减一。
cdl.countDown();
}
}.start();
}
System.out.println("等待所有运动员就位...");
try {
// 等待倒计数变为 0。
cdl.await();
System.out.println("比赛开始。");
} catch (InterruptedException ex) {
}
运行输出(可能)为:
运动员开始就位。
1 号运动员到场并开始准备...
2 号运动员到场并开始准备...
4 号运动员到场并开始准备...
等待所有运动员就位...
8 号运动员到场并开始准备...
6 号运动员到场并开始准备...
3 号运动员到场并开始准备...
7 号运动员到场并开始准备...
5 号运动员到场并开始准备...
6 号运动员就位。
1 号运动员就位。
5 号运动员就位。
4 号运动员就位。
7 号运动员就位。
8 号运动员就位。
2 号运动员就位。
3 号运动员就位。
比赛开始。
从上面的例子还可以看出 CountDownLatch
的局限性和 CompletionService
类似,在于无法处理子任务数量不确定的情况,例如统计某个文件夹中的文件数量。另外,如果某个子任务在调用 countDown
之前就挂掉了,倒计数就永远不会归零。对于这种情况,要么用 finally
之类的手段保证 countDown
一定会被调用,要么用带参数的 await
方法指定超时时间。