烟台小学网站建设,自媒体平台注册官网下载,wordpress 表格处理,wordpress官网appCountDownLatch
概念
CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。
CountDownLatch 定义了一个计数器#xff0c;和一个阻塞队列#xff0c; 当计数器的值递减为0之前#xff0c;阻塞队列里面的线程处于挂起状态#xff0c;当计数器递减到0时…CountDownLatch
概念
CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。
CountDownLatch 定义了一个计数器和一个阻塞队列 当计数器的值递减为0之前阻塞队列里面的线程处于挂起状态当计数器递减到0时会唤醒阻塞队列所有线程这里的计数器是一个标志可以表示一个任务一个线程也可以表示一个倒计时器CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。
CountDownLatch 常用方法说明
CountDownLatch(int count); //构造方法创建一个值为count 的计数器。 await();//阻塞当前线程将当前线程加入阻塞队列。 await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行 countDown();//对计数器进行递减1操作当计数器递减至0时当前线程会去唤醒阻塞队列里的所有线程。
用CountDownLatch 来优化我们的报表统计
功能现状
运营系统有统计报表、业务为统计每日的用户新增数量、订单数量、商品的总销量、总销售额......等多项指标统一展示出来因为数据量比较大统计指标涉及到的业务范围也比较多所以这个统计报表的页面一直加载很慢所以需要对统计报表这块性能需进行优化。
问题分析
统计报表页面涉及到的统计指标数据比较多每个指标需要单独的去查询统计数据库数据单个指标只要几秒钟但是页面的指标有10多个所以整体下来页面渲染需要将近一分钟。
解决方案
任务时间长是因为统计指标多而且指标是串行的方式去进行统计的我们只需要考虑把这些指标从串行化的执行方式改成并行的执行方式那么整个页面的时间的渲染时间就会大大的缩短 如何让多个线程同步的执行任务我们这里考虑使用多线程每个查询任务单独创建一个线程去执行这样每个统计指标就可以并行的处理了。
要求
因为主线程需要每个线程的统计结果进行聚合然后返回给前端渲染所以这里需要提供一种机制让主线程等所有的子线程都执行完之后再对每个线程统计的指标进行聚合。 这里我们使用CountDownLatch 来完成此功能。
模拟代码
1、分别统计4个指标用户新增数量、订单数量、商品的总销量、总销售额
2、假设每个指标执行时间为3秒。如果是串行化的统计方式那么总执行时间会为12秒。
3、我们这里使用多线程并行开启4个子线程分别进行统计
4、主线程等待4个子线程都执行完毕之后返回结果给前端。
//用于聚合所有的统计指标private static Map mapnew HashMap();//创建计数器这里需要统计4个指标private static CountDownLatch countDownLatchnew CountDownLatch(4);
public static void main(String[] args) {
//记录开始时间long startTimeSystem.currentTimeMillis();
Thread countUserThreadnew 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 countOrderThreadnew 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 countGoodsThreadnew 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 countmoneyThreadnew 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 endTimeSystem.currentTimeMillis();//记录结束时间System.out.println(------统计指标全部完成--------);System.out.println(统计结果为map.toString());System.out.println(任务总执行时间为(endTime-startTime)/1000秒);
} catch (InterruptedException e) {e.printStackTrace();}
}
执行结果 CountDownLatch实现原理
1、创建计数器
当我们调用CountDownLatch countDownLatchnew CountDownLatch(4) 时候此时会创建一个AQS的同步队列并把创建CountDownLatch 传进来的计数器赋值给AQS队列的 state所以state的值也代表CountDownLatch所剩余的计数次数 public CountDownLatch(int count) {if (count 0) throw new IllegalArgumentException(count 0);this.sync new Sync(count);//创建同步队列并设置初始计数器值}
2、阻塞线程
当我们调用countDownLatch.wait()的时候会创建一个节点加入到AQS阻塞队列并同时把当前线程挂起。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
判断计数器是技术完毕未完毕则把当前线程加入阻塞队列 public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}
构建阻塞队列的双向链表挂起当前线程 3、计数器递减
当我们调用countDownLatch.down()方法的时候会对计数器进行减1操作AQS内部是通过释放锁的方式对state进行减1操作当state0的时候证明计数器已经递减完毕此时会将AQS阻塞队列里的节点线程全部唤醒。 public void countDown() {//递减锁重入次数当state0时唤醒所有阻塞线程sync.releaseShared(1);}
public final boolean releaseShared(int arg) {//递减锁的重入次数if (tryReleaseShared(arg)) {doReleaseShared();//唤醒队列所有阻塞的节点return true;}return false;}private void doReleaseShared() {//唤醒所有阻塞队列里面的线程for (;;) {Node h head;if (h ! null h ! tail) {int ws h.waitStatus;if (ws Node.SIGNAL) {//节点是否在等待唤醒状态if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始continue;unparkSuccessor(h);//成功则唤醒线程}else if (ws 0 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue; // loop on failed CAS}if (h head) // loop if head changedbreak;}}