前言
有了前几章博客铺垫,本篇介绍一下CountDownLatch在项目中的实战。看本篇之前可以去看看之前的几篇
写博客是自己对知识梳理,目前是写给自己看,算是自己学习后的作业,也是为了养成一个良好的习惯。
一、业务需求
需求:
每一个班级对应多个学科,现在要去新增一个根据班级的维度去批量生成其对应的所有学科试卷PDF,
并将所有的试卷pdf打包zip下载。
分析:
典型的多任务并行待处理完后汇总处理。
1. 通过班级id查询到其对应的学科及学科下的试题;
2. 通过多线程并行处理学科的试题按规则生成PDF试卷;
3. 待多线程都处理完后主线程再去获取所有生成PDF试卷并打包。
二、业务实现
demo代码如下:
/**
* 生成试卷
*/
public String createPaper(String classId){
long start = System.currentTimeMillis();
//查询所对应班级的学科试题
List<String> subjectList = subjectList(classId);
//创建一个核心线程数10,最大线程容量20 ,60s存活,有界容量是20的队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,20,
60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(20));
//试卷生成的总数量
CountDownLatch totalPaper = new CountDownLatch(subjectList.size());
//多线程创建试卷
subjectList.forEach(subject->{
executor.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
//生成试卷业务,并上传到 fastDFS
if("语文".equals(subject)){
Thread.sleep(2000);
}else {
Thread.sleep(1000);
}
log.info("生成:{} 试卷pdf成功,花费时间为:{}ms",subject,System.currentTimeMillis()-start);
//减少试卷
totalPaper.countDown();
}
});
});
try {
//试卷没有生成完主线程一直阻塞
totalPaper.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//关闭线程池
executor.shutdown();
log.info("开始打包试卷成zip,生成所有试卷的时间为:{}ms",System.currentTimeMillis()-start);
//所有的试卷都生成后,将所有试卷打包zip
return fileToZip(classId);
}
/**
* 打包试卷
* @param classId
* @return
*/
private String fileToZip(String classId){
//查询到刚刚上传的所有试卷
//打包zip,并上传到fastDFS上
log.info("打包试卷成功,班级id:{}",classId);
return "www.pdf.com";
}
/**
* 查询班级的科目及对应的题目
* @param classId classId
* @return
*/
private List<String> subjectList(String classId){
log.info("查询班级科目和试卷,班级id:{}",classId);
List<String> subjectList = new ArrayList<>();
subjectList.add("数学");
subjectList.add("语文");
subjectList.add("英语");
subjectList.add("物理");
subjectList.add("化学");
subjectList.add("生物");
return subjectList;
}
三、执行结果
nio-8080-exec-2 为主线程;pool-1-thread-x 为子线程;
可以很直观当所有的子线程 pool-1-thread-x 都执行完之后主线程才会执行才会去执行nio-8080-exec-2的打包业务。
四、CountDownLatch
1. 什么是CountDownLatch?
ountDownLatch与其他并发编程工具类,如CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue等在java.util.concurrent包中与JDK 1.5一起被引入。
CountDownLatch能让一个java线程等待其他线程完成任务,比如Application的主线程等待,直到其他负责启动框架服务的服务线程完成所有服务的启动。
2. CountDownLatch是如何工作?
CountDownLatch用线程数来初始化一个计数器,每当一个线程完成执行时,这个计数器就会递减。当计数为零时,表示所有线程都已完成执行,处于等待状态的主线程可以继续执行。
3. 如何使用CountDownLatch?
可以先初始化CountDownLatch计数器,然后通过 countDown()原子性自减 配合 await() 同步阻塞(计数器>0时会同步阻塞),
可以完美处理多任务汇总的业务。
4. 如何使用CountDownLatch和CyclicBarrier区别?
CountDownLatch 倒计时器 不能重置计数;
CyclicBarrier 循环栅栏 作用和CountDownLatch差不多,但是可以支持重置计数。
总结
在遇到多任务汇总处理的业务时推荐 使用多线程和CountDownLatch。