1.背景
- CountDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。
- 存在于java.util.cucurrent包下。
2.概念
- CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
- 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有的线程都执行完毕,然后在闭锁上等待的线程就可以恢复执行接下来的任务。
3.源码
-
CountDownLatch类中只提供了一个构造器
构造一个CountDownLatch,用给定计数值初始化
参数 count为在线程可以通过await方法之前必须调用countDown的次数
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
- 类中有三个方法最重要
//调用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() { };
4.示例
4.1.示例一
4.1.1.示例代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName:CountDownLatchTest2
* @Description TODO
* @Create By login name:zkrsun
* @Date 2021/9/17 14:26
*/
public class CountDownLatchTest2 {
public static class Work implements Runnable {
private final int num;
private final CountDownLatch latch;
public Work(int num, CountDownLatch latch) {
this.num = num;
this.latch = latch;
}
public void run() {
System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
doWork();
latch.countDown();
System.out.println("子线程"+Thread.currentThread().getName()+"结束执行");
}
void doWork() {
System.out.println("===num ="+num+"====count ="+latch.getCount());
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
int num = 10;
CountDownLatch latch = new CountDownLatch(num);
for (int i = 0; i < num; i++) {
executorService.execute(new Work(i,latch));
}
System.out.println("主线程"+Thread.currentThread().getName()+"等待子线程执行完成...");
latch.await();
System.out.println("主线程"+Thread.currentThread().getName()+"开始执行...");
if(!executorService.isShutdown()){
executorService.shutdown();
}
}
}
4.1.2.示例结果
子线程pool-1-thread-1开始执行
主线程main等待子线程执行完成...
===num =0====count =10
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =1====count =9
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =2====count =8
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =3====count =7
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =4====count =6
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =5====count =5
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =6====count =4
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =7====count =3
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =8====count =2
子线程pool-1-thread-1结束执行
子线程pool-1-thread-1开始执行
===num =9====count =1
子线程pool-1-thread-1结束执行
主线程main开始执行...
4.1.3.代码解析
1.创建一个work类实现Runnable接口,实现run方法
2.创建一个线程数为一的线程池
3.每个线程按顺序执行,每个线程执行完毕计数器的计数值减一
4.此时主线程执行await()方法后的代码
4.2.示例二
4.2.1.示例代码
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName:CountDownLatchTest1
* @Description TODO
* @Create By login name:zkrsun
* @Date 2021/9/17 14:22
*/
public class CountDownLatchTest1 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
final CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"开始执行");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
System.out.println("主线程"+Thread.currentThread().getName()+"等待子线程执行完成...");
System.out.println("latch.await()after:"+latch.getCount());
latch.await();
System.out.println("latch.await()before:"+latch.getCount());
System.out.println("主线程"+Thread.currentThread().getName()+"开始执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
}
4.2.2.示例结果
子线程pool-1-thread-3开始执行
主线程main等待子线程执行完成...
子线程pool-1-thread-1开始执行
子线程pool-1-thread-2开始执行
latch.await()after:3
子线程pool-1-thread-3执行完毕
子线程pool-1-thread-1执行完毕
子线程pool-1-thread-2执行完毕
latch.await()before:0
主线程main开始执行...
4.2.3.代码解析
1.创建了一个线程数为三的线程池
2.构造了一个计数值为3的计数器
3.三个线程同时执行,打印线程名并给予线程随机休眠时间,每个线程执行完毕后,调用countDown()方法将计数值减1
4.三个线程执行的同时主线程也同时执行直到遇到await()方法停止执行,等待计数器归零
5.当三个线程全部执行完毕后计数器的计数值归为0此时主线程开始执行后续代码
5.项目实战
5.1.项目需求
计算批次指数,存在对于环比的计算比如对于销售金额的月环比,需要获取上个月的数据跟本月的数据进行对比。
5.2.使用CountDownLatch的原因
一开始设计为串行获取,但是遇到bi获取数据结果过慢的问题改为多线程获取数据
多线程获取数据存在一个问题:有的线程执行快,有的线程执行慢导致最终的结果报错
此时引用CountDownLatch,等待本月数据以及上月数据获取完毕后主线程再计算最终结果
5.2.项目代码
只提供Controller代码后续底层代码过多不一一展示
@GetMapping("/getBatchIndexData")
public Object getBatchIndexData(@RequestParam(name = "startTime", required = false) String startTime,
@RequestParam(name = "endTime", required = false) String endTime,
@RequestParam(name = "phone", required = false) String phone,
@RequestParam(name = "dayType", required = false) Integer dayType) {
if (startTime == null) {
startTime = DateUtil.getDateByNumber(-1, "yyyy-MM-dd");
}
if (endTime == null) {
endTime = DateUtil.getDateByNumber(0, "yyyy-MM-dd");
}
if (dayType == null) {
dayType = 0;
}
String yesterdayStartTime;
String yesterdayEndTime;
//获取时间间隔
Integer days = DateUtil.getDayBySubtract(endTime, startTime, "yyyy-MM-dd");
String temp = " 00:05:00";
//判断日期类型
if (dayType == 0) {
//按天查找
if (days <= 1 && DateUtil.isToday(startTime)) {
//如果为今天实时数据
startTime = DateUtil.getDateByNumber(0, "yyyy-MM-dd 00:05:00");
endTime = DateUtil.getDateByNumber(0, "yyyy-MM-dd HH:mm:ss");
yesterdayStartTime = DateUtil.getDateByNumber(-1, "yyyy-MM-dd 00:05:00");
yesterdayEndTime = DateUtil.getDateByNumber(-1, "yyyy-MM-dd HH:mm:ss");
} else {
startTime += temp;
endTime += temp;
yesterdayStartTime = DateUtil.newgetDateByNumber(-days, "yyyy-MM-dd", startTime)+temp;
yesterdayEndTime = startTime;
}
} else {
//按月查找
startTime += temp;
endTime += temp;
yesterdayStartTime = DateUtil.newgetbyMonthDateByNumber(-1, "yyyy-MM-dd", startTime)+temp;
yesterdayEndTime = DateUtil.newgetbyMonthDateByNumber(-1, "yyyy-MM-01", endTime)+temp;
}
System.out.println("[批次系列API]开始时间:" + startTime);
System.out.println("结束时间:" + endTime);
System.out.println("上周期开始时间:" + yesterdayStartTime);
System.out.println("上周期结束时间:" + yesterdayEndTime);
//开启线程异步处理
CountDownLatch countDownLatch = new CountDownLatch(2);
//第一个子线程获取本周期数据
// ExecutorService es1 = Executors.newFixedThreadPool(5);
final List<BusinessSeriesData>[] todayBusinessDataList = new List[4];
String finalStartTime = startTime;
String finalEndTime = endTime;
int finalDayType = dayType;
executor.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + "执行开始");
if ("**********".equals(phone) || "**********".equals(phone) || "**********".equals(phone)) {
batchDataService.initFour(finalStartTime, finalEndTime);
} else {
batchDataService.init(finalStartTime, finalEndTime);
}
todayBusinessDataList[0] = batchDataService.getWarehouseDataList();
todayBusinessDataList[1] = batchDataService.getAreaDataList();
todayBusinessDataList[2] = batchDataService.getBatchIndexDataTotal();
todayBusinessDataList[3] = batchDataService.getShopDataList();
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + "异常:");
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行成功");
countDownLatch.countDown();
});
//第二个子线程获取上周期数据
final List<BusinessSeriesData>[] yesterdayBusinessDataList = new List[4];
executor.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + "执行开始");
if ("**********".equals(phone) || "**********".equals(phone) || "**********".equals(phone)) {
batchDataService.initFour(finalStartTime, finalEndTime);
} else {
batchDataService.init(yesterdayStartTime, yesterdayEndTime);
}
yesterdayBusinessDataList[0] = batchDataService.getWarehouseDataList();
yesterdayBusinessDataList[1] = batchDataService.getAreaDataList();
yesterdayBusinessDataList[2] = batchDataService.getBatchIndexDataTotal();
yesterdayBusinessDataList[3] = batchDataService.getShopDataList();
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + "异常:");
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行成功");
countDownLatch.countDown();
});
System.out.println("等待两个线程执行完毕…… ……");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("两个子线程都执行完毕,继续执行主线程");
List<BusinessSeriesData> shopDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[3], yesterdayBusinessDataList[3]);
List<BusinessSeriesData> warehouseDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[0], yesterdayBusinessDataList[0]);
List<BusinessSeriesData> areaDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[1], yesterdayBusinessDataList[1]);
List<BusinessSeriesData> totalDataList = indexDataManager.calculateIncreaseData(todayBusinessDataList[2], yesterdayBusinessDataList[2]);
HashMap<String, Object> result = new HashMap<>();
result.put("shopDataList", shopDataList);
result.put("warehouseDataList", warehouseDataList);
result.put("areaDataList", areaDataList);
result.put("totalDataList", totalDataList);
System.out.println("[批次系列API]结束:" + startTime);
return ResponseUtil.ok(result);
}