1.引子
天气稍微有点冷!从本篇文章开始,我们来到高级并发编程系列中的并发流程控制小节了。这里你能先简单思考一下,什么是并发流程控制?为什么需要并发流程控制吗?
我们来简单的捋一下,在我们生活中,合作、协作是办成一件事情的关键。那么并发编程场景一样,多线程一起完成某个任务,自然是需要相互之间的协作了。我们来看几个例子,比如说:
- 运动会上,百米赛跑运动员起跑前,需要等到发令枪响后,才能一起起跑
- 同样运动会上,还是百米赛跑,终点裁判员需要等待所有运动员到达后,才能统计排名运动员的成绩
- 网上购物,凑团拼团,需要凑足多少人后,才能完成拼团订单
- 热门旅游景点,因为容量有限,需要排队依次有序参观等等
你看到了上面这些我们生活中的例子,相互存在着依赖关系,需要在协作下完成。我们编程中一样,编程最终是为了解决生活中的问题,生活中需要相互协作,线程之间自然也需要相互协作了。
问题是有哪些手段,可以让我们在编写并发程序的时候,实现线程之间的协作呢?在Juc包中,给我们提供下列工具:
- 倒计时门栓CountDownLatch,实现一等多,或者多等一编程模型
- 循环栅栏CyclicBarrier,实现凑团拼团编程模型
- 信号量Semaphore,实现热门旅游景点容量有限,需要买票参观编程模型
- 条件原语Condition,实现生产者消费者编程模型
倒计时门栓、循环栅栏、信号量、条件都是实现线程协作的工具,也是我们这个小节的主要内容。好了,今天就让我们一起先来看倒计时门栓CountDownLatch的使用。
2.案例
2.1.代码演示
案例描述:
- 模拟百米赛跑中,5个运动员在起跑线做好准备,等待裁判员发起发令枪
- 当裁判员发起发令枪响后,5个运动员同时起跑
- 实现并发编程协作中,多等一的编程模型(5个运动员,等待1个发令枪)
/**
* 线程协作之CountDownLatch
*
* @author ThinkPad
* @version 1.0
* @date 2021/1/10 20:39
*/
public class CountDownLatchDemo {
/**
* 发令枪
*/
public static CountDownLatch raceCommand = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
// 1.创建5个运动员,一起参加百米竞赛
Runnable raceTask = new RaceTask();
for(int i = 0; i < 5; i++){
new Thread(raceTask, "运动员【" + i + "】").start();
}
// 2.主线程,通过CountDownLatch的countDown方法(模拟发令枪响)
Thread.sleep(3);
System.out.println("------倒数3.2.1后,裁判员发起发令枪.------");
raceCommand.countDown();
}
/**
* 百米赛跑运动员
*/
static class RaceTask implements Runnable{
@Override
public void run() {
// 获取运动员名称
String name = Thread.currentThread().getName();
System.out.println(name + "准备好了,等待发令枪.");
try {
// 通过CountDownLatch的await方法,实现等待发令枪响
raceCommand.await();
// 运动员起跑,竞赛中(随机休眠时间,模拟运动员跑步花费时间)
System.out.println("发令枪响了," + name + "起跑.");
int time = new Random().nextInt(1000);
Thread.sleep(time);
// 运动员到达终点
System.out.println( name + "到达终点.共花费时间:" + time + "毫秒.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
运动员【0】准备好了,等待发令枪.
运动员【2】准备好了,等待发令枪.
运动员【3】准备好了,等待发令枪.
运动员【1】准备好了,等待发令枪.
运动员【4】准备好了,等待发令枪.
------倒数3.2.1后,裁判员发起发令枪.------
发令枪响了,运动员【2】起跑.
发令枪响了,运动员【4】起跑.
发令枪响了,运动员【1】起跑.
发令枪响了,运动员【0】起跑.
发令枪响了,运动员【3】起跑.
运动员【0】到达终点.共花费时间:87毫秒.
运动员【3】到达终点.共花费时间:428毫秒.
运动员【1】到达终点.共花费时间:564毫秒.
运动员【4】到达终点.共花费时间:669毫秒.
运动员【2】到达终点.共花费时间:893毫秒.
2.2.关键知识点分析
上面的案例,我们实现了现实生活中,百米竞赛运动员等候发令枪响,多等一的编程模型。你看到通过CountDownLatch实现线程协作,是非常容易的。你需要关注的有三个地方。
- 构造CountDownLatch对象
// 倒计时门栓,构造函数中的数字值,表示需要倒数的次数
CountDownLatch raceCommand = new CountDownLatch(1);
- 实现运动员阻塞等待的方法
// 通过CountDownLatch的await方法,实现等待发令枪响
// 调用await方法后,线程阻塞直到倒计时倒数为0后,才能继续运行
raceCommand.await();
- 实现发令枪的倒数计数方法
// 2.主线程,通过CountDownLatch的countDown方法(模拟发令枪响)
// 倒数次数,与构造方法中的数字值一致
raceCommand.countDown();