1、CountDownLatch 概念
CountDownLatch可以使一个或多个线程等待其他线程各自执行完毕后再执行。
CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。
2、CountDownLatch 常用方法说明
CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。
await();//阻塞当前线程,将当前线程加入阻塞队列。
await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行,
countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。
3、用CountDownLatch 来优化我们的报表统计
- 功能现状
运营系统有统计报表、业务为统计每日的用户新增数量、订单数量、商品的总销量、总销售额…等多项指标统一展示出来,因为数据量比较大,统计指标涉及到的业务范围也比较多,所以这个统计报表的页面一直加载很慢,所以需要对统计报表这块性能需进行优化。
-
问题分析
统计报表页面涉及到的统计指标数据比较多,每个指标需要单独的去查询统计数据库数据,单个指标只要几秒钟,但是页面的指标有10多个,所以整体下来页面渲染需要将近一分钟。 -
解决方案
任务时间长是因为统计指标多,而且指标是串行的方式去进行统计的,我们只需要考虑把这些指标从串行化的执行方式改成并行的执行方式,那么整个页面的时间的渲染时间就会大大的缩短, 如何让多个线程同步的执行任务,我们这里考虑使用多线程,每个查询任务单独创建一个线程去执行,这样每个统计指标就可以并行的处理了。 -
要求
因为主线程需要每个线程的统计结果进行聚合,然后返回给前端渲染,所以这里需要提供一种机制让主线程等所有的子线程都执行完之后再对每个线程统计的指标进行聚合。 这里我们使用CountDownLatch 来完成此功能。 -
模拟代码
1、分别统计4个指标用户新增数量、订单数量、商品的总销量、总销售额;
2、假设每个指标执行时间为3秒。如果是串行化的统计方式那么总执行时间会为12秒。
3、我们这里使用多线程并行,开启4个子线程分别进行统计
4、主线程等待4个子线程都执行完毕之后,返回结果给前端。
//用于聚合所有的统计指标
private static Map map=new HashMap();
//创建计数器,这里需要统计4个指标
private static CountDownLatch countDownLatch=new CountDownLatch(4);
public static void main(String[] args) {
//记录开始时间
long startTime=System.currentTimeMillis();
Thread countUserThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在统计新增用户数量");
Thread.sleep(3000);//任务执行需要3秒
map.put("userNumber",1);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计新增用户数量完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread countOrderThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在统计订单数量");
Thread.sleep(3000);//任务执行需要3秒
map.put("countOrder",2);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计订单数量完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread countGoodsThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在商品销量");
Thread.sleep(3000);//任务执行需要3秒
map.put("countGoods",3);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计商品销量完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread countmoneyThread=new Thread(new Runnable() {
public void run() {
try {
System.out.println("正在总销售额");
Thread.sleep(3000);//任务执行需要3秒
map.put("countmoney",4);//保存结果值
countDownLatch.countDown();//标记已经完成一个任务
System.out.println("统计销售额完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动子线程执行任务
countUserThread.start();
countGoodsThread.start();
countOrderThread.start();
countmoneyThread.start();
try {
//主线程等待所有统计指标执行完毕
countDownLatch.await();
long endTime=System.currentTimeMillis();//记录结束时间
System.out.println("------统计指标全部完成--------");
System.out.println("统计结果为:"+map.toString());
System.out.println("任务总执行时间为"+(endTime-startTime)/1000+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
类似业务代码:
迁移资源数据,根据资源类型批量插入数据,先查询资源类型遍历,线程池并行执行各个资源类型的数据,countDownLatch进行标记,计算迁移数据时间。
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedTreadPool(5);
//记录开始时间
long startTime=System.currentTimeMillis();
List<Map<String,Object>> mainMoTypeList = nmsItresDao.queryMainMoTypeList();
CountDownLatch countDownLatch=new CountDownLatch(mainMoTypeList.size());
for(Map<String,Object> map : mainMoTypeList){
executorService.execute(
new Runnable(){
public void run() {
try {
String moTypeId = (String)map.get("mo_type_id");
String moTypeCode = (String)map.get("mo_type_code");
List<Map<String,Object>> result = nmsItresDao.queryItres(moTypeId,moTypeCode);
if(result.size() == 0){
return;
}
nmsItresDao.batchAddResourceObject(result);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
countDownLatch.countDown();//标记已经完成一个任务
}
}
}
);
}
try {
//主资源类型数据迁移完成,统计时间
countDownLatch.await();
long endTime=System.currentTimeMillis();//记录结束时间
System.out.println("------主资源类型数据迁移全部完成--------");
System.out.println("任务总执行时间为"+(endTime-startTime)/1000+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}