CountDownLatch的理解与使用
CountDownLatch简介
CountDownLatch是一个并发编程工具类,使用计数器实现,可使一个线程等待其他线程完成各自工作后再继续执行,常用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是互斥)作用。
使用步骤
- 初始化 ,指定待完成线程的个数;
CountDownLatch latch = new CountDownLatch(3);
- 每个待完成线程执行完成后,调用countDown()将计数器减1;
latch.countDown();
- 调用await()方法使等待线程处于阻塞状态,当计数到达0时,所有待完成线程执行完毕,等待线程继续执行;
latch.await();
典型用法及实现
1. 某一线程在开始运行前等待n个线程执行完毕
- 将CountDownLatch的计数器初始化为n;
- 每当一个任务线程执行完毕,调用countDown(),计数器减1;
- 当计数器的值减为0时,在CountDownLatch上await() 的线程被唤醒。
一个典型的应用场景就是老师分发完试卷,等到同学们统一提交后再进行批阅。
import java.util.concurrent.CountDownLatch;
public class CountDownLatch {
public static void main(String[] args) throws InterruptedException {
// 假如一共三个考生
CountDownLatch latch = new CountDownLatch(3);
// 三个考生分别耗时1,2,3s完成卷子
StudentThread first = new StudentThread(1000, latch, "stu1");
StudentThread second = new StudentThread(2000, latch, "stu2");
StudentThread third = new StudentThread(3000, latch, "stu3");
// 考试铃响,开始发卷
long start = System.currentTimeMillis();
System.out.println("开始发卷...");
first.start();
second.start();
third.start();
// 老师等待所有考生提交试卷
latch.await();
// 所有考生完成提交试卷
System.out.println("所有试卷提交完成!");
}
static class StudentThread extends Thread {
private int delay;
private CountDownLatch latch;
public StudentThread(int delay, CountDownLatch latch, String name) {
super(name);
this.delay = delay;
this.latch = latch;
}
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "拿到试卷");
Thread.sleep(delay);
System.out.println(Thread.currentThread().getName() + "提交试卷");
// 每提交一份试卷,待提交数减一
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出
开始发卷…
stu1拿到试卷
stu2拿到试卷
stu3拿到试卷
stu1提交试卷
stu2提交试卷
stu3提交试卷
所有试卷提交完成!
可以在代码中调用以下方法来显示各线程的执行时间,帮助理解。
System.currentTimeMillis()
输出
开始发卷…1586868630585
stu1拿到试卷1586868630585
stu2拿到试卷1586868630585
stu3拿到试卷1586868630585
stu1提交试卷1586868631585
stu2提交试卷1586868632586
stu3提交试卷1586868633586
所有试卷提交完成!1586868633587
2. 实现多个线程开始执行任务的最大并行性
这里是并行,不是并发,强调的是多个线程在某一时刻同时开始执行。
- 初始化一个共享的CountDownLatch(1);
- 多个线程在开始执行任务前首先调用await();
- 当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
一个典型的例子是赛跑,将多个线程放到起点,等待裁判员发令枪响后,多个线程同时开跑。
import java.util.concurrent.CountDownLatch;
public class testLatch {
public static void main(String[] args) {
CountDownLatch begin = new CountDownLatch(1);//begin由裁判员发令执行,裁判员countDown,选手await
CountDownLatch end = new CountDownLatch(2);//end由2个选手完成比赛后执行,选手countDown,裁判员await
Thread thread1 = new Thread(new Player(begin,end,1000),"选手1");
// Player1用时1000ms完成比赛
Thread thread2 = new Thread(new Player(begin,end,1001),"选手2");
// Player2用时1001ms完成比赛
thread1.start();
thread2.start();
try{
System.out.println("比赛开始..." + System.currentTimeMillis());
begin.countDown();//begin由1变为0后,选手起跑
end.await();//裁判员等待选手完成比赛
System.out.println("比赛结束!" + System.currentTimeMillis());
}catch(Exception e){
e.printStackTrace();
}
}
}
class Player implements Runnable{
private CountDownLatch begin;
private CountDownLatch end;
private int delay;
Player(CountDownLatch begin,CountDownLatch end,int delay){
this.begin = begin;
this.end = end;
this.delay = delay;
}
public void run() {
try {
begin.await();//选手需等待begin信号
System.out.println(Thread.currentThread().getName() + "起跑!" + System.currentTimeMillis());
Thread.sleep(delay);
System.out.println(Thread.currentThread().getName() + "到达!" + System.currentTimeMillis());
end.countDown();//countDown()计数减一,当end.getCount()为0时,裁判员线程会自动唤醒,宣告比赛结束
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出
比赛开始…1586869849332
选手1起跑!1586869849334
选手2起跑!1586869849334
选手1到达!1586869850334
选手2到达!1586869850335
比赛结束!1586869850335
感想
纸上得来终觉浅,绝知此事要躬行。希望自己多动手,多动脑,少动嘴,每天吃饱睡好好!