1.基本概念
CountDownLatch,中文名倒数闩,jdk并发工具包中一个并发控制器,它抽象了一个常见的多线程并发场景,开发人员使用它可以写出同时兼顾线程安全性与高效率的代码。
2.抽象模型
相当于是一种进化版本的等待/通知机制,它可以的实现的是一个或多个工作线程完成任务后通知一个或多个等待线程开始工作,jdk中的await/notify、notifyAll是一个工作线程完成任务通知一个等待线程或所有等待的线程。
3.使用场景
运动员田径跑步比赛
4.CountDownLatch使用api
<1>CountDownLatch latch = new CountDownLatch(n); //初始化倒数闩
<2>latch.await()或latch.await(long timeout,TimeUnit unit); //执行线程等待,直到latch倒数为0(超时后中断或线程被中断)
<3>latch.countDown(); //执行线程把latch倒数数减1,直到为0,会通知所有被这个计数器阻塞的线程;如果当前计数器latch为0,调用countDown(),不会继续减1;
5.使用示例(田径比赛)
public static void main(String[] args) throws InterruptedException {
CountDownLatch beginLatch = new CountDownLatch(1); // 起跑倒数闩
CountDownLatch endLatch = new CountDownLatch(10); // 结束倒数闩
// 创建10个线程,代表是个运动员,他们的主要任务就是预备准备、和跑步两个阶段
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 1; i < 11; i++) {
final int num = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
// 准备阶段
long a = (long) (Math.random() * 1000);
Thread.sleep(a);
System.out.println("运动员" + num +"号,准备了" + a + "s,准备完毕!");
beginLatch.await();
// 跑步阶段
long b = (long) (Math.random() * 100);
Thread.sleep(b);
System.out.println("运动员" + num + "号,用时" + b + "s,完成比赛");
endLatch.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
executor.submit(runnable);
}
//出发信号发出
beginLatch.countDown();
endLatch.await();
System.out.println("跑步结束");
}
细心的你会发现上述的代码存在致命的错误就是当主线程发出了开跑信号beginLatch.countDown(),可能有部分运动员还在准备。运行结果如下:
运动员10号,准备了155s,准备完毕!
运动员10号,用时31s,完成比赛
运动员8号,准备了228s,准备完毕!
运动员8号,用时61s,完成比赛
运动员9号,准备了333s,准备完毕!
运动员3号,准备了335s,准备完毕!
运动员9号,用时61s,完成比赛
运动员3号,用时94s,完成比赛
运动员4号,准备了513s,准备完毕!
运动员4号,用时71s,完成比赛
运动员2号,准备了598s,准备完毕!
运动员6号,准备了616s,准备完毕!
运动员6号,用时39s,完成比赛
运动员2号,用时54s,完成比赛
运动员1号,准备了737s,准备完毕!
运动员5号,准备了805s,准备完毕!
运动员1号,用时80s,完成比赛
运动员5号,用时70s,完成比赛
运动员7号,准备了923s,准备完毕!
运动员7号,用时67s,完成比赛
跑步结束
没有都准备好就开始,控制不合理
根本原因在于这里缺少一个倒数闩,来计数准备好的运动员。我们进行改进如下
public static void main(String[] args) throws InterruptedException {
CountDownLatch readyLatch = new CountDownLatch(10); //准备好倒数闩
CountDownLatch beginLatch = new CountDownLatch(1); // 起跑倒数闩
CountDownLatch endLatch = new CountDownLatch(10); // 结束倒数闩
// 创建10个线程,代表是个运动员,他们的主要任务就是预备准备、和跑步两个阶段
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 1; i < 11; i++) {
final int num = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
// 准备阶段
long a = (long) (Math.random() * 1000);
Thread.sleep(a);
System.out.println("运动员" + num +"号,准备了" + a + "s,准备完毕!");
readyLatch.countDown();
beginLatch.await();
// 跑步阶段
long b = (long) (Math.random() * 100);
Thread.sleep(b);
System.out.println("运动员" + num + "号,用时" + b + "s,完成比赛");
endLatch.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
executor.submit(runnable);
}
readyLatch.await();
//出发信号发出
beginLatch.countDown();
endLatch.await();
System.out.println("跑步结束");
}
改进后运行结果
运动员8号,准备了112s,准备完毕!
运动员3号,准备了119s,准备完毕!
运动员5号,准备了463s,准备完毕!
运动员6号,准备了579s,准备完毕!
运动员2号,准备了597s,准备完毕!
运动员1号,准备了825s,准备完毕!
运动员7号,准备了849s,准备完毕!
运动员9号,准备了876s,准备完毕!
运动员4号,准备了931s,准备完毕!
运动员10号,准备了930s,准备完毕!
运动员6号,用时0s,完成比赛
运动员5号,用时19s,完成比赛
运动员9号,用时24s,完成比赛
运动员8号,用时56s,完成比赛
运动员3号,用时62s,完成比赛
运动员1号,用时77s,完成比赛
运动员7号,用时84s,完成比赛
运动员4号,用时85s,完成比赛
运动员2号,用时88s,完成比赛
运动员10号,用时86s,完成比赛
跑步结束