前言

项目有许多统计的功能,有些统计页面,要展示几个统计的结果,用户通过前台设置相关参数,后台实时统计并返回数据。后台查询正常查询结果是串行的。
最好的用户体验,就是每一个操作都可以实时的展示数据,3秒之内应该是用户的忍受范围之内的了,所以做一款产品不仅要考虑用户交互设计,后端的优化也是比不可少的。
举一个物流项目例子:
1:统计订单量
2:统计物流信息的时效
3:统计客户下单量(按高到低排序)
大家可以简单的看下以上这3项统计数据,总体来说,统计量还是不少的。最主要的还是要实时、实时、实时(重要的事情说三遍),显然定时任务是不现实的。

优化前:
程序的逻辑

优化后:
程序的逻辑

并行常见如:发邮件通知,短信通知等。
这里我们把并行的场景应用在统计这里。

多任务并行处理,适用于多核CPU,单核CPU多线程执行任务可能会适得其反(上下文切换以及线程的创建和销毁都会消耗资源),特别是CPU密集型的任务。

一:多任务并行处理

代码实现:
这里以demo实现,只需要把示例代码替换成业务代码即可

public class CountDownLatch Test {final static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");final static String beginTime = simpleDateFormat.format(new Date());public static void main(String[] args) throws Exception{CountDownLatch latch = new CountDownLatch(3);//Gaci gaci1 = new Gaci("任务1", 1000, latch);Gaci gaci2 = new Gaci("任务2", 2000, latch);Gaci gaci3 = new Gaci("任务3", 2000, latch);gaci1.start();// 任务1开始执行gaci2.start();// 任务2开始执行gaci3.start();// 任务3开始执行latch.await();// 等待所有任务结束System.out.println("所有的统计任务执行完成:" + simpleDateFormat.format(new Date()));}static class Gaci extends Thread {String gaciName;// 名称int runTime;// 模拟业务代码运行时间CountDownLatch latch;// latchpublic Gaci(String gaciName, int runTime, CountDownLatch latch) {this.gaciName = gaciName;this.runTime = runTime;this.latch = latch;}public void run() {try {System.out.println(gaciName+ " begin  "+ beginTime);// 模拟任务执行时间// 执行业务代码System.out.println(gaciName+ " service code  "+ beginTime);Thread.sleep(runTime);System.out.println(gaciName + " end "+ simpleDateFormat.format(new Date()));latch.countDown();// 单次任务结束,计数器减一} catch (InterruptedException e) {e.printStackTrace();}}}
}

它会等所有任务结束之后,再往下走后面的代码,这个时候你就可以封装数据返回给前端了。

由于我们的业务是要同步返回统计数据的,所以定时任务,队列等不适用,我们使用到的是CountDownLatch,看源码,是jdk1.5新增的特性,提供的并发工具类.

CountDownLatch主要用于同步一个或多个任务,强制它们等待,由其他任务执行的一组操作完成。CountDownLatch典型的用法是将一个程序分为N个互相独立的可解决任务,并创建值为N的CountDownLatch(new CountDownLatch(N),N代表多少个任务)。当每一个任务完成时,都会在这个锁存器上调用countDown(latch.countDown(),代表当前任务已经执行完,计数器减一,直到计数器减完。),等待问题被解决的任务调用这个锁存器的await,将他们自己拦住,直至锁存器计数结束。
我们解释下我们的demo代码。
首先我们创建CountDownLatch
3个任务

  CountDownLatch latch = new CountDownLatch(3);

然后我们定义3个任务执行

 Gaci gaci1 = new Gaci("任务1", 1000, latch);Gaci gaci2 = new Gaci("任务2", 2000, latch);Gaci gaci3 = new Gaci("任务3", 2000, latch);gaci1.start();// 任务1开始执行gaci2.start();// 任务2开始执行gaci3.start();// 任务3开始执行

我们先看看任务执行代码,我们每一个任务最后都会有一个latch.countDown()方法,这个方法就是上述文字写的,代表当前任务已经执行完,计数器减一

        try {System.out.println(gaciName+ " begin  "+ beginTime);// 模拟任务执行时间// 执行业务代码System.out.println(gaciName+ " service code  "+ beginTime);Thread.sleep(runTime);System.out.println(gaciName + " end "+ simpleDateFormat.format(new Date()));latch.countDown();// 单次任务结束,计数器减一} catch (InterruptedException e) {e.printStackTrace();}

我们再往下看

 latch.await();// 等待所有任务结束System.out.println("所有的统计任务执行完成:" + simpleDateFormat.format(new Date()));latch.await();

就是说的上述拦住上面的任务,等所有任务执行完成之后,再往下走。

二:多任务并行+线程池处理

上述简述了多任务并行的处理方式,首先频繁的创建、销毁对象是一个很消耗性能的事情;
如果用户量比较大,导致占用过多的资源,可能会导致我们的服务由于资源不足而宕机;
综上所述,在实际的开发中,这种操作其实是不可取的一种方式。
线程池中线程的使用率提升,减少对象的创建、销毁;
线程池可以控制线程数,有效的提升服务器的使用资源,避免由于资源不足而发生宕机等问题;
理论上来讲,线程越多程序可能更快,但是在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗,以及保护操作系统本身的目的。我们通常需要将线程限制在一定的范围之类,线程池就起到了这样的作用。
继续优化后:

首先我们了解下Java的线程池
Java的Executors的四种线程池介绍:
1:newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2:newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
3:newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
4:newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

使用线程池的优点
1:可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
2:提供定时执行、定期执行、单线程、并发数控制等功能。
3:重用存在的线程,减少对象创建、消亡的开销,性能佳。

4种线程池的优缺点:
1:newCachedThreadPool
不足:这种方式虽然可以根据业务场景自动的扩展线程数来处理我们的业务,但是最多需要多少个线程同时处理缺是我们无法控制的;
优点:如果当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务创建的线程,并不会重新创建新的线程,提高了线程的复用率;
2:newFixedThreadPool
优点:newFixedThreadPool的线程数是可以进行控制的,因此我们可以通过控制最大线程来使我们的服务器打到最大的使用率,同事又可以保证及时流量突然增大也不会占用服务器过多的资源。
3:newScheduledThreadPool
讲两点:
3.1 scheduleAtFixedRate
如果间隔时间大于任务的执行时间,任务不受执行时间的影响。如果间隔时间小于任务的执行时间,那么任务执行结束之后,会立马执行,至此间隔时间就会被打乱。
3.2 scheduleWithFixedDelay
间隔时间不会受任务执行时间长短的影响
4:newSingleThreadExecutor
这是一个单线程池,至始至终都由一个线程来执行。

直接上代码实现:

public class CountDownLatchTest {final static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");final static String beginTime = simpleDateFormat.format(new Date());/*** IO密集型任务:  线程个数为CPU核数的两倍。到其中的线程在IO操作的时候,其他线程可以继续* 用cpu,提高了cpu的利用率 * 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)* CPU密集型任务: 线程个数为CPU核数。这几个线程可以并行执行,不存在线程切换到开销,提* 高了cpu的利用率的同时也减少了切换线程导致的性能损耗* 一般为CPU核心数+1(常出现于线程中:复杂算法)* 混合型任务:视机器配置和复杂度自测而定*/private static int corePoolSize = Runtime.getRuntime().availableProcessors();/*** public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,*                           TimeUnit unit,BlockingQueue<Runnable> workQueue)* 参考源码参数代表什么意思* corePoolSize 指定核心线程数量* maximumPoolSize 指定最大线程数* keepAliveTime和TimeUnit 指定线程空闲后的最大存活时间* workQueue 这是线程池的缓冲队列,还未执行的线程会在队列中等待,监控队列的长度,确保队列是有界限的* 不适当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。* 而过多的线程又会由于频繁的上下文切换导致整个系统的速度变缓。队列的长度重要,它必须得是有界限的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求。* ExecutorService 默认的实现是一个无界的 LinkedBlockingQueue。*/private static ThreadPoolExecutor executor  = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(1000));public static void main(String[] args) throws Exception{//        CountDownLatch latch = new CountDownLatch(3);//
//        Gaci gaci1 = new Gaci("任务1", 1000, latch);
//        Gaci gaci2 = new Gaci("任务2", 2000, latch);
//        Gaci gaci3 = new Gaci("任务3", 2000, latch);
//
//        gaci1.start();//任务1开始执行
//        gaci2.start();//任务2开始执行
//        gaci3.start();//任务3开始执行
//
//        latch.await();// 等待所有任务结束
//        System.out.println("所有的统计任务执行完成:" + simpleDateFormat.format(new Date()));// 线程池方式 -- startCountDownLatch latch = new CountDownLatch(3);//使用execute方法executor.execute(new Gaci("任务1", 1000, latch));executor.execute(new Gaci("任务2", 1000, latch));executor.execute(new Gaci("任务3", 2000, latch));latch.await();// 等待所有任务结束System.out.println("所有的统计任务执行完成:" + simpleDateFormat.format(new Date()));// 线程池方式 -- endexecutor.shutdown();}static class Gaci extends Thread {String gaciName;// 名称int runTime;// 模拟业务代码运行时间CountDownLatch latch;// latchpublic Gaci(String gaciName, int runTime, CountDownLatch latch) {this.gaciName = gaciName;this.runTime = runTime;this.latch = latch;}public void run() {try {System.out.println(gaciName+ " begin  "+ beginTime);//模拟任务执行时间// 执行业务代码System.out.println(gaciName+ " service code  "+ beginTime);Thread.sleep(runTime);System.out.println(gaciName + " end "+ simpleDateFormat.format(new Date()));latch.countDown();//单次任务结束,计数器减一} catch (InterruptedException e) {e.printStackTrace();}}}
}

执行效率对比
效率如下:
多线程并行任务+线程池-》多线程并行任务-》单线程串行

countDownLatch源码分析后续补上

Java+CountDownLatch多任务处理优化相关推荐

  1. 百万并发中间件系统的内核设计看Java并发性能优化

    " 这篇文章,给大家聊聊一个百万级并发的中间件系统的内核代码里的锁性能优化. 很多同学都对Java并发编程很感兴趣,学习了很多相关的技术和知识.比如volatile.Atomic.synch ...

  2. 35 个 Java 代码性能优化总结

    http://mp.weixin.qq.com/s?__biz=MjM5MzMyNzg0MA==&mid=400312907&idx=3&sn=fee2e15f000b25e5 ...

  3. JAVA源码优化、分析工具

    JAVA源码优化.分析工具 一.11款用于优化.分析源代码的Java工具 1. PMD from http://pmd.sourceforge.net/ PMD能够扫描Java 源代码,查找类似以下的 ...

  4. java代码统计收藏量_干货收藏 | 35个Java 代码性能优化总结(上)

    原标题:干货收藏 | 35个Java 代码性能优化总结(上) 前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这 ...

  5. Java中性能优化的35种方法汇总

    原文地址:http://www.jb51.net/article/102831.htm 前言 对程序员们来说,代码优化是一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于 ...

  6. java代码优化_java代码之美(11)---java代码的优化

    java代码的优化 随着自己做开发时间的增长,越来越理解雷布斯说的: 敲代码要像写诗一样美.也能理解有一次面试官问我你对代码有洁癖吗? 一段好的代码会让人看就像诗一样,也像一个干净房间会让人看去很舒服 ...

  7. [译]GC专家系列5-Java应用性能优化的原则

    原文链接:http://www.cubrid.org/blog/dev-platform/the-principles-of-java-application-performance-tuning/ ...

  8. 44个Java代码性能优化总结

    转载自 44个Java代码性能优化总结 代码优化的最重要的作用应该是:避免未知的错误.在代码上线运行的过程中,往往会出现很多我们意想不到的错误,因为线上环境和开发环境是非常不同的,错误定位到最后往往是 ...

  9. java常见性能优化_十大最常见的Java性能问题

    java常见性能优化 Java性能是所有Java应用程序开发人员都关心的问题,因为快速使应用程序与使其正常运行同等重要. 史蒂文·海恩斯(Steven Haines)使用他在Java性能问题上的个人经 ...

最新文章

  1. java异常处理之throw, throws,try和catch
  2. PHP 学习笔记 01
  3. c++ 深度优先搜索(迷宫)
  4. abb变频器电机过热保护怎么复位_变频器驱动的电机过热该怎么办
  5. 第十八天:规划风险管理和识别风险
  6. Jmeter接口测试使用beanshell断言json返回
  7. elasticsearch中集群选举中的ping源码解析
  8. 关于滑轮组的计算机知识点,初中物理:滑轮及滑轮组知识点总结
  9. 人工智能之语音识别技术【科普】
  10. 【个人笔记 - 目录】OpenCV4 C++ 快速入门 30讲
  11. C++中派生类的构造函数
  12. android apk 加密
  13. Java二维码生成代码
  14. 7、Horizon 虚拟桌面登录
  15. C#汉字转拼音_Microsoft.PinYinConverter汉字转拼音
  16. 对垒以太网10BASE-T1S,CAN XL能后来居上么?
  17. 回溯法,子集选择合集
  18. leetcode -- 953验证外星语词典
  19. Redis 的 RDB 和 AOF
  20. 电脑外设(I/O)简介:键盘鼠标

热门文章

  1. 李想这三年主要做了什么?
  2. matlab对于点云栅格化,并且提取路面(粗糙版)
  3. 吉大考博英语是计算机答题吗,2018 吉大考博经历分享
  4. win10安全中心(win10安全中心怎么关闭)
  5. Linux下NFS服务器的配置
  6. 一个 Java 猿眼中 Vue3 和 Vue2 的差异
  7. php区块链以太坊,兄弟连区块链教程以太坊源码分析CMD深入分析(一)
  8. PY “不是内部或外部命令,也不是可运行的程序或批处理文件”
  9. 第三方软件测试(软件检测)收费标准
  10. Android RecyclerView多样式列表实践指南