前言

前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑。当然了,优化是无止境的,前人栽树后人乘凉。作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序。

改造

理论上讲,线程越多程序可能更快,但是在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗,以及保护操作系统本身的目的。我们通常需要将线程限制在一定的范围之类,线程池就起到了这样的作用。

程序逻辑

一张图能解决的问题,就应该尽可能的少BB,当然底层原理性的东西还是需要大家去记忆并理解的。

Java 线程池

Java通过Executors提供四种线程池,分别为:

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

优点

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

代码实现

方式一(CountDownLatch)
  1. /**
  2. * 多任务并行+线程池统计
  3. * 创建者 科帮网 https://blog.52itstyle.com
  4. * 创建时间 2018年4月17日
  5. */
  6. public class StatsDemo {
  7. final static SimpleDateFormat sdf = new SimpleDateFormat(
  8. "yyyy-MM-dd HH:mm:ss");
  9. final static String startTime = sdf.format(new Date());
  10. /**
  11. * IO密集型任务 = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
  12. * CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法)
  13. * 混合型任务 = 视机器配置和复杂度自测而定
  14. */
  15. private static int corePoolSize = Runtime.getRuntime().availableProcessors();
  16. /**
  17. * public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
  18. * TimeUnit unit,BlockingQueue<Runnable> workQueue)
  19. * corePoolSize用于指定核心线程数量
  20. * maximumPoolSize指定最大线程数
  21. * keepAliveTime和TimeUnit指定线程空闲后的最大存活时间
  22. * workQueue则是线程池的缓冲队列,还未执行的线程会在队列中等待
  23. * 监控队列长度,确保队列有界
  24. * 不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。
  25. * 而过多的线程又会 由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求。
  26. * ExecutorService 默认的实现是一个无界的 LinkedBlockingQueue。
  27. */
  28. private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
  29. new LinkedBlockingQueue<Runnable>(1000));
  30. public static void main(String[] args) throws InterruptedException {
  31. CountDownLatch latch = new CountDownLatch(5);
  32. //使用execute方法
  33. executor.execute(new Stats("任务A", 1000, latch));
  34. executor.execute(new Stats("任务B", 1000, latch));
  35. executor.execute(new Stats("任务C", 1000, latch));
  36. executor.execute(new Stats("任务D", 1000, latch));
  37. executor.execute(new Stats("任务E", 1000, latch));
  38. latch.await();// 等待所有人任务结束
  39. System.out.println("所有的统计任务执行完成:" + sdf.format(new Date()));
  40. }
  41. static class Stats implements Runnable {
  42. String statsName;
  43. int runTime;
  44. CountDownLatch latch;
  45. public Stats(String statsName, int runTime, CountDownLatch latch) {
  46. this.statsName = statsName;
  47. this.runTime = runTime;
  48. this.latch = latch;
  49. }
  50. public void run() {
  51. try {
  52. System.out.println(statsName+ " do stats begin at "+ startTime);
  53. //模拟任务执行时间
  54. Thread.sleep(runTime);
  55. System.out.println(statsName + " do stats complete at "+ sdf.format(new Date()));
  56. latch.countDown();//单次任务结束,计数器减一
  57. } catch (InterruptedException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
  62. }
方式二(Future)
  1. /**
  2. * 多任务并行+线程池统计
  3. * 创建者 科帮网 https://blog.52itstyle.com
  4. * 创建时间 2018年4月17日
  5. */
  6. public class StatsDemo {
  7. final static SimpleDateFormat sdf = new SimpleDateFormat(
  8. "yyyy-MM-dd HH:mm:ss");
  9. final static String startTime = sdf.format(new Date());
  10. /**
  11. * IO密集型任务 = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
  12. * CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法)
  13. * 混合型任务 = 视机器配置和复杂度自测而定
  14. */
  15. private static int corePoolSize = Runtime.getRuntime().availableProcessors();
  16. /**
  17. * public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
  18. * TimeUnit unit,BlockingQueue<Runnable> workQueue)
  19. * corePoolSize用于指定核心线程数量
  20. * maximumPoolSize指定最大线程数
  21. * keepAliveTime和TimeUnit指定线程空闲后的最大存活时间
  22. * workQueue则是线程池的缓冲队列,还未执行的线程会在队列中等待
  23. * 监控队列长度,确保队列有界
  24. * 不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。
  25. * 而过多的线程又会 由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求。
  26. * ExecutorService 默认的实现是一个无界的 LinkedBlockingQueue。
  27. */
  28. private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
  29. new LinkedBlockingQueue<Runnable>(1000));
  30. public static void main(String[] args) throws InterruptedException {
  31. List<Future<String>> resultList = new ArrayList<Future<String>>();
  32. //使用submit提交异步任务,并且获取返回值为future
  33. resultList.add(executor.submit(new Stats("任务A", 1000)));
  34. resultList.add(executor.submit(new Stats("任务B", 1000)));
  35. resultList.add(executor.submit(new Stats("任务C", 1000)));
  36. resultList.add(executor.submit(new Stats("任务D", 1000)));
  37. resultList.add(executor.submit(new Stats("任务E", 1000)));
  38. //遍历任务的结果
  39. for (Future<String> fs : resultList) {
  40. try {
  41. System.out.println(fs.get());//打印各个线任务执行的结果,调用future.get() 阻塞主线程,获取异步任务的返回结果
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. } catch (ExecutionException e) {
  45. e.printStackTrace();
  46. } finally {
  47. //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
  48. executor.shutdown();
  49. }
  50. }
  51. System.out.println("所有的统计任务执行完成:" + sdf.format(new Date()));
  52. }
  53. static class Stats implements Callable<String> {
  54. String statsName;
  55. int runTime;
  56. public Stats(String statsName, int runTime) {
  57. this.statsName = statsName;
  58. this.runTime = runTime;
  59. }
  60. public String call() {
  61. try {
  62. System.out.println(statsName+ " do stats begin at "+ startTime);
  63. //模拟任务执行时间
  64. Thread.sleep(runTime);
  65. System.out.println(statsName + " do stats complete at "+ sdf.format(new Date()));
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. return call();
  70. }
  71. }
  72. }

执行时间

以上代码,均是伪代码,下面是2000+个学生的真实测试记录。

  1. 2018-04-17 17:42:29.284 INFO 测试记录81e51ab031eb4ada92743ddf66528d82-单线程顺序执行,花费时间:3797
  2. 2018-04-17 17:42:31.452 INFO 测试记录81e51ab031eb4ada92743ddf66528d82-多线程并行任务,花费时间:2167
  3. 2018-04-17 17:42:33.170 INFO 测试记录81e51ab031eb4ada92743ddf66528d82-多线程并行任务+线程池,花费时间:1717

SpringBoot开发案例之多任务并行+线程池处理的更多相关文章

  1. springboot之多任务并行+线程池处理

    最近项目中做到一个关于批量发短信的业务,如果用户量特别大的话,不能使用单线程去发短信,只能尝试着使用多任务来完成!我们的项目使用到了方式二,即Future的方案 Java 线程池 Java通过Exec ...

  2. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 ​最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场 ...

  3. SpringBoot开发案例之整合Dubbo分布式服务

    前言 在 SpringBoot 很火热的时候,阿里巴巴的分布式框架 Dubbo 不知是处于什么考虑,在停更N年之后终于进行维护了.在之前的微服务中,使用的是当当维护的版本 Dubbox,整合方式也是使 ...

  4. 【玩转SpringBoot】异步任务执行与其线程池配置

    同步代码写起来简单,但就是怕遇到耗时操作,会影响效率和吞吐量. 此时异步代码才是王者,但涉及多线程和线程池,以及异步结果的获取,写起来颇为麻烦. 不过在遇到SpringBoot异步任务时,这个问题就不 ...

  5. Springboot学习笔记(一)-线程池的简化及使用

    工作中经常涉及异步任务,通常是使用多线程技术,比如线程池ThreadPoolExecutor,它的执行规则如下: 在Springboot中对其进行了简化处理,只需要配置一个类型为java.util.c ...

  6. SpringBoot异步使用@Async原理及线程池配置

    前言 在实际项目开发中很多业务场景需要使用异步去完成,比如消息通知,日志记录,等非常常用的都可以通过异步去执行,提高效率,那么在Spring框架中应该如何去使用异步呢 使用步骤 完成异步操作一般有两种 ...

  7. SpringBoot开发案例之整合Kafka实现消息队列

    前言 最近在做一款秒杀的案例,涉及到了同步锁.数据库锁.分布式锁.进程内队列以及分布式消息队列,这里对SpringBoot集成Kafka实现消息队列做一个简单的记录. Kafka简介 Kafka是由A ...

  8. SpringBoot开发案例之打造私有云网盘

    前言 最近在做工作流的事情,正好有个需求,要添加一个附件上传的功能,曾找过不少上传插件,都不是特别满意.无意中发现一个很好用的开源web文件管理器插件 elfinder,功能比较完善,社区也很活跃,还 ...

  9. SpringBoot开发案例之整合Activiti工作流引擎

    前言 JBPM是目前市场上主流开源工作引擎之一,在创建者Tom Baeyens离开JBoss后,JBPM的下一个版本jBPM5完全放弃了jBPM4的基础代码,基于Drools Flow重头来过,目前官 ...

随机推荐

  1. 企业必会技能 tomcat

    企业必会技能 tomcat tomcat   一.什么是Tomcat? Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项 ...

  2. ngix_http_stub_status_module

    1.ngx_http_stub_status_module 是一个 Nginx 的内置 HTTP 模块,该模块可以提供 Nginx 的状态信息.默认情况下这个模块是不被编译进来的,所以在编译 Ngin ...

  3. 设计模式 --> (11)桥接模式

    桥接模式 将抽象部分与它的实现部分分离,使它们都可以独立地变化. 适用性: 1.当一个对象有多个变化因素的时候,考虑依赖于抽象的实现,而不是具体的实现.如上面例子中手机品牌有2种变化因素,一个是品牌, ...

  4. C语言描述链表的实现及操作

    一.链表的创建操作 // 操作系统 win 8.1 // 编译环境 Visual Stuido 2017 #include<stdio.h> #include<malloc.h> ...

  5. 第一次使用github、git工具,本地仓库、远程仓库使用

    一次使用git,记录下使用过程...可能还有很多东西可能还没理解,后期理解了再写吧 git是什么.,百度的回答: 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 使用过程一直在百度 ...

  6. postman简单教程,使用tests模块来验证接口时是否通过

    接口测试醉重要的就是返回数据的检查,一个简单的接口,我们可以肉眼检查返回数据,但接口一旦多起来且复杂,每次的检查都会很费劲,此时我们就需要postman 的tests模块来代替 概念: Postman ...

  7. [poj2185]Milking Grid_KMP

    Milking Grid poj-2185 题目大意:给出一个字符矩阵,求最小覆盖矩阵(可以残余). 注释:$1\le R\le 10^5$,$1\le C \le 75$ 想法:和bzoj1355不 ...

  8. alpha-咸鱼冲刺day3

    一,合照 emmmmm.自然还是没有的. 二,项目燃尽图 三,项目进展 今天把数据库的表给建好了,学长那边把登陆跟注册页面也做好了(纯页面,html5+css的那种) 四,问题困难 日常啥都不会,百度 ...

  9. Linux下进程间通信的六种机制详解

    linux下进程间通信的几种主要手段:        1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具 ...

  10. Alpha冲刺Day12

    Alpha冲刺Day12 一:站立式会议 今日安排: 由黄腾飞和张梨贤继续完成政府人员模块下的风险管控子模块下的分级统计展示 由林静继续完成企业注册模块 由周静平完成登录页面模块 二:实际项目进展 人 ...