高并发(8)- 线程并发工具类-CountDownLatch
2020-04-02 本文已影响0人
残冬十九
@[TOC](高并发(8)- 线程并发工具类-CountDownLatch)
前言
上篇文章讲解了线程的并发工具类之ForkJoin,本文就来讲解CountDownLatch并发工具类
ForkJoin的核心思想是分而治之
CountDownLatch的则是一组线程等待其他的线程完成工作以后在执行。
什么是CountDownLatch
CountDownLatch是一组线程等待其他线程工作完成以后在执行。例如:一个框架在启动的时候,需要加载各种功能,只有当这些功能加载完成之后,才可以运行主线程,这个时候就可以用到CountDownLatch了,CountDownLatch通过计数器来实现,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了
CountDownLatch执行流程如图所示,我们定义了一个计数器cnt = 5,TW1和TW2两个线程执行了await()方法,就需要等待计数器减少为0.
- 所以Td执行了一个操作,CountDown了一次,cnt-1=4
- Tb线程也操作了一次,CountDown了一次,cnt-1=3
- Td线程又操作了一次,CountDown了一次,cnt-1=2
- Ta线程也操作了一次,CountDown了一次,cnt-1=1
- Tc线程也操作了一次,CountDown了一次,cnt-1=0
因为这时候cnt计数器=0所以,TW1和TW2两个线程也便推出了等待,继续运行
注意
- 一个CountDownLatch可以被多个线程等待,而不是只能被一个线程使用,如图所示TW1和TW2用的同一个CountDownLatch。
- 一个线程也可以多次CountDown操作,如图所示,Td线程进行了两次CountDown操作。
- 一个线程在CountDown之后还是可以继续运行的,不会在CountDown之后停止,如图所示,Ta和Td两个线程在CountDown之后继续运行。
CountDownLatch实现
- countDownLatch类中只提供了一个构造器:
//参数count为计数值
public CountDownLatch(int count) {};
- countDownLatch类中还有三个方法比较重要
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
- 具体代码实现
/**
* @version 1.0
* @Description CountDownLatchDemo
* @Author wb.yang
*/
public class CountDownLatchDemo {
/**
* 创建一个计数器为6的CountDownLatch
*/
static CountDownLatch latch = new CountDownLatch(6);
/**
* 初始化线程(只有一步,有4个)
*/
private static class InitThread implements Runnable {
@Override
public void run() {
System.out.println("Thread_" + Thread.currentThread().getId()
+ " 准备初始化工作......");
//初始化线程完成工作了,countDown方法只扣减一次;
latch.countDown();
System.out.println("Thread_" + Thread.currentThread().getId()
+ " .继续做剩余的工作");
}
}
/**
* 业务线程
*/
private static class BusinessThread implements Runnable {
@Override
public void run() {
try {
// 等待
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("BusiThread_" + Thread.currentThread().getId()
+ "运行中-----");
}
}
public static void main(String[] args) throws InterruptedException {
//单独的初始化线程,初始化分为2步,需要扣减两次
new Thread(new Runnable() {
@Override
public void run() {
SleepTools.ms(1);
System.out.println("Thread_" + Thread.currentThread().getId()
+ "准备初始化工作第一步......");
latch.countDown();//每完成一步初始化工作,扣减一次
System.out.println("开始第二步工作.......");
SleepTools.ms(1);
System.out.println("Thread_" + Thread.currentThread().getId()
+ "准备初始化工作第二步......");
latch.countDown();//每完成一步初始化工作,扣减一次
}
}).start();
//开始主要业务线程
new Thread(new BusinessThread()).start();
for (int i = 0; i <= 3; i++) {
//执行三次初始化工作
Thread thread = new Thread(new InitThread());
thread.start();
}
latch.await();
System.out.println("main方法工作........");
}
}
代码中可以看到,我们定义了一个CountDownLatch,计数器为6,然后以一个业务线程,需要等待CountDownLatch执行完之后才能执行。还有六个初始化线程。
- 运行一个初始化线程
- 运行业务线程
- 进行剩下的四初始化线程
- 主线程等待await,等待CountDownLatch
再看下运行结果
CountDownLatch运行结果
我们可以看到,先是初始化线程运行,进行了两次CountDown,证明一个线程可以多次CountDown,然后是剩下四个初始化线程进行CountDown操作,并且在CountDown操作后技术执行代码,然后是主业务线程在CountDown为0的时候执行,由于主线程也是await,所以也需要CountDown为0才可以执行,也证明了一个CountDownLatch可以被多个线程使用。