CountDownLatch 是一个用来倒计数的咚咚。如果某项任务可以拆分成若干个子任务同时进行,然后等待所有的子任务完成,可以考虑使用它。
该类的用法非常简单。首先构造一个 CountDownLatch
,唯一的参数是任务数量,一旦构造完毕就不能修改。接着启动所有的子任务(线程),且每个子任务在完成自己的计算后,调用 CountDownLatch#countDown
方法将倒计数减一。最后在主线程中调用 CountDownLatch#await
方法等待计数器归零。
例如赛跑的准备阶段,八名运动员先后到达起点做好准备,然后裁判打响发令枪,准备工作就结束了,比赛开始。如果把从运动员就位到发令枪响看做赛跑准备任务,那么每个运动员的准备过程就是其子任务,可以用 CountDownLatch
模拟如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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
方法指定超时时间。