Springboot 中异步线程的使用
在过往的后台开发中,我们往往使用java自带的线程或线程池,来进行异步的调用。这对于效果来说没什么,甚至可以让开发人员对底层的状况更清晰,但是对于代码的易读性和可维护性却非常的差。
开发人员在实际使用过程中,应该更多的将精力放置在业务代码的书写过程中,而不是系统代码的维护中。你需要懂,但是不需要你直接维护去写,这才是编程语言的风向标。(这也是为什么spring在目前的java开发中,占用比重如此之大的原因之一)
下面来看使用Springboot 来实现异步调用的集中场景
一、简易注解,无需额外配置
1、添加@EnableAsync 到启动类(或者线程池配置类中)
2、添加@Async到需要异步执行的方法中
代码如下:

启动类

  1. 1 @EnableAsync
  2. 2 @SpringBootApplication
  3. 3 public class DemoLearnSpringbootApplication {
  4. 4
  5. 5 public static void main(String[] args) {
  6. 6 SpringApplication.run(DemoLearnSpringbootApplication.class, args);
  7. 7 }
  8. 8 }

调用类

  1. 1 @Component
  2. 2 public class SimpleAsyncDemo {
  3. 3 @Autowired
  4. 4 private SimpleTaskHandler simpleTaskHandler;
  5. 5
  6. 6
  7. 7 @PostConstruct
  8. 8 public void execTaskHandler1() {
  9. 9 try {
  10. 10 simpleTaskHandler.handle1(2);
  11. 11 simpleTaskHandler.handle2(2);
  12. 12 simpleTaskHandler.handle3(2);
  13. 13 simpleTaskHandler.handle1(2);
  14. 14 simpleTaskHandler.handle2(2);
  15. 15 simpleTaskHandler.handle3(2);
  16. 16 simpleTaskHandler.handle1(2);
  17. 17 simpleTaskHandler.handle2(2);
  18. 18 simpleTaskHandler.handle3(2);
  19. 19 } catch (InterruptedException e) {
  20. 20 e.printStackTrace();
  21. 21 }
  22. 22 }
  23. 23
  24. 24 }

被异步调用的类

  1. 1 @Component
  2. 2 public class SimpleTaskHandler {
  3. 3
  4. 4 public void printCurrentTime(String key) {
  5. 5 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  6. 6 System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
  7. 7 }
  8. 8
  9. 9 @Async
  10. 10 public void handle1(int time) throws InterruptedException {
  11. 11 TimeUnit.SECONDS.sleep(time);
  12. 12 printCurrentTime("handle1");
  13. 13 }
  14. 14
  15. 15 @Async
  16. 16 public void handle2(int time) throws InterruptedException {
  17. 17 TimeUnit.SECONDS.sleep(time);
  18. 18 printCurrentTime("handle2");
  19. 19 }
  20. 20
  21. 21 @Async
  22. 22 public void handle3(int time) throws InterruptedException {
  23. 23 TimeUnit.SECONDS.sleep(time);
  24. 24 printCurrentTime("handle3");
  25. 25 }
  26. 26
  27. 27
  28. 28 }

执行结果

handle1、handle2、handle3的执行结果为乱序,不可预估。这样最简易的通过2个注解即完成异步线程的调用了。
细心的同学已经发现了,连续调用9次异步线程后,最后一次的线程名称就会与之前的重复。这是由于默认的线程池配置的结果。

默认配置如下

  1. # 核心线程数
  2. spring.task.execution.pool.core-size=8
  3. # 最大线程数
  4. spring.task.execution.pool.max-size=16
  5. # 空闲线程存活时间
  6. spring.task.execution.pool.keep-alive=60s
  7. # 是否允许核心线程超时
  8. spring.task.execution.pool.allow-core-thread-timeout=true
  9. # 线程队列数量
  10. spring.task.execution.pool.queue-capacity=100
  11. # 线程关闭等待
  12. spring.task.execution.shutdown.await-termination=false
  13. spring.task.execution.shutdown.await-termination-period=
  14. # 线程名称前缀
  15. spring.task.execution.thread-name-prefix=task-

二、自定义线程池
只通过注解来完成异步线程调用,简单明了,对应的异步线程来自springboot 默认生成的异步线程池。但是有些场景却并不满足。所以我们需要针对业务需要定义自己的线程池配置文件
1、在application.properties中定义我们自己的线程池配置
2、在springboot项目中,添加对应的线程池bean对象
3、添加@EnableAsync 到启动类(或者线程池配置类中)
4、添加@Async到需要异步执行的方法中
代码如下:

application.properties配置文件

  1. task.pool.demo.corePoolSize= 5
  2. task.pool.demo.maxPoolSize= 10
  3. task.pool.demo.keepAliveSeconds= 300
  4. task.pool.demo.queueCapacity= 50

调用类

  1. 1 @Component
  2. 2 public class SimpleAsyncDemo {
  3. 3
  4. 4 @Autowired
  5. 5 private PoolTaskHandler poolTaskHandler;
  6. 6
  7. 7
  8. 8 @PostConstruct
  9. 9 public void execTaskHandler2() {
  10. 10 try {
  11. 11 poolTaskHandler.handle1(2);
  12. 12 poolTaskHandler.handle2(2);
  13. 13 poolTaskHandler.handle3(2);
  14. 14 poolTaskHandler.handle1(2);
  15. 15 poolTaskHandler.handle2(2);
  16. 16 poolTaskHandler.handle3(2);
  17. 17 poolTaskHandler.handle1(2);
  18. 18 poolTaskHandler.handle2(2);
  19. 19 poolTaskHandler.handle3(2);
  20. 20 } catch (InterruptedException e) {
  21. 21 e.printStackTrace();
  22. 22 }
  23. 23 }
  24. 24
  25. 25 }

异步线程池的配置类

  1. 1 @Configuration
  2. 2 public class ThreadPoolConfig {
  3. 3
  4. 4 @Value("${task.pool.demo.corePoolSize}")
  5. 5 private int corePoolSize;
  6. 6 @Value("${task.pool.demo.maxPoolSize}")
  7. 7 private int maxPoolSize;
  8. 8 @Value("${task.pool.demo.queueCapacity}")
  9. 9 private int queueCapacity;
  10. 10 @Value("${task.pool.demo.keepAliveSeconds}")
  11. 11 private int keepAliveSeconds;
  12. 12
  13. 13
  14. 14 @Bean("handleAsync")
  15. 15 public TaskExecutor taskExecutor() {
  16. 16 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  17. 17 // 设置核心线程数
  18. 18 executor.setCorePoolSize(corePoolSize);
  19. 19 // 设置最大线程数
  20. 20 executor.setMaxPoolSize(maxPoolSize);
  21. 21 // 设置队列容量
  22. 22 executor.setQueueCapacity(queueCapacity);
  23. 23 // 设置线程活跃时间(秒)
  24. 24 executor.setKeepAliveSeconds(keepAliveSeconds);
  25. 25 // 设置默认线程名称前缀
  26. 26 executor.setThreadNamePrefix("Thread-ABC-");
  27. 27 // 设置拒绝策略
  28. 28 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  29. 29 // 等待所有任务结束后再关闭线程池
  30. 30 executor.setWaitForTasksToCompleteOnShutdown(true);
  31. 31 return executor;
  32. 32 }
  33. 33 }

被异步调用的类

  1. 1 @Component
  2. 2 public class PoolTaskHandler {
  3. 3
  4. 4 public void printCurrentTime(String key) {
  5. 5 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  6. 6 System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
  7. 7 }
  8. 8
  9. 9 @Async("handleAsync")
  10. 10 public void handle1(int time) throws InterruptedException {
  11. 11 TimeUnit.SECONDS.sleep(time);
  12. 12 printCurrentTime("handle-1");
  13. 13 }
  14. 14
  15. 15 @Async("handleAsync")
  16. 16 public void handle2(int time) throws InterruptedException {
  17. 17 TimeUnit.SECONDS.sleep(time);
  18. 18 printCurrentTime("handle-2");
  19. 19 }
  20. 20
  21. 21 @Async("handleAsync")
  22. 22 public void handle3(int time) throws InterruptedException {
  23. 23 TimeUnit.SECONDS.sleep(time);
  24. 24 printCurrentTime("handle-3");
  25. 25 }
  26. 26
  27. 27
  28. 28 }

执行结果如下

与上例类似,我们发现请求线程变成了每5个一批,这与我们在配置文件中的配置互相印证

调用类

  1. 1 @Component
  2. 2 public class SimpleAsyncDemo {
  3. 3
  4. 4 @Autowired
  5. 5 private ReturnTaskHandler returnTaskHandler;
  6. 6
  7. 7 @PostConstruct
  8. 8 public void execTaskHandler3() {
  9. 9 try {
  10. 10 String a1 = returnTaskHandler.handle1(2);
  11. 11 String a2 = returnTaskHandler.handle2(2);
  12. 12 String a3 = returnTaskHandler.handle3(2);
  13. 13 String a4 = returnTaskHandler.handle1(2);
  14. 14 String a5 = returnTaskHandler.handle2(2);
  15. 15 String a6 = returnTaskHandler.handle3(2);
  16. 16 String a7 = returnTaskHandler.handle1(2);
  17. 17 String a8 = returnTaskHandler.handle2(2);
  18. 18 String a9 = returnTaskHandler.handle3(2);
  19. 19 int c = 1;
  20. 20 } catch (InterruptedException e) {
  21. 21 e.printStackTrace();
  22. 22 }
  23. 23 }
  24. 24
  25. 25 }

被调用类

  1. 1 @Component
  2. 2 public class ReturnTaskHandler {
  3. 3
  4. 4 public void printCurrentTime(String key) {
  5. 5 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  6. 6 System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
  7. 7 }
  8. 8
  9. 9 @Async("handleAsync")
  10. 10 public String handle1(int time) throws InterruptedException {
  11. 11 TimeUnit.SECONDS.sleep(time);
  12. 12 printCurrentTime("handle-1");
  13. 13 return "result1";
  14. 14 }
  15. 15
  16. 16 @Async("handleAsync")
  17. 17 public String handle2(int time) throws InterruptedException {
  18. 18 TimeUnit.SECONDS.sleep(time);
  19. 19 printCurrentTime("handle-2");
  20. 20 return "result2";
  21. 21 }
  22. 22
  23. 23 @Async("handleAsync")
  24. 24 public String handle3(int time) throws InterruptedException {
  25. 25 TimeUnit.SECONDS.sleep(time);
  26. 26 printCurrentTime("handle-3");
  27. 27 return "result3";
  28. 28 }
  29. 29
  30. 30 }

其余代码继续我们使用上文中的其他代码
结果如下

所有结果返回都是null值。
如果想要拿到正确的执行结果,我们需要使用future接口类看来帮忙接住异步线程的返回结果(关于future等接口类的内容我会在后边的文章中讲解)
其余代码继续我们使用上文中的其他代码,改动的代码如下:
被调用类

  1. 1 @Component
  2. 2 public class ReturnSuccTaskHandler {
  3. 3
  4. 4 public void printCurrentTime(String key) {
  5. 5 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  6. 6 System.out.println(format.format(new Date()) + "***" + key + "****" + Thread.currentThread().getName());
  7. 7 }
  8. 8
  9. 9 @Async("handleAsync")
  10. 10 public Future<String> handle1(int time) throws InterruptedException {
  11. 11 TimeUnit.SECONDS.sleep(time);
  12. 12 printCurrentTime("handle-1");
  13. 13 return new AsyncResult<>("result1");
  14. 14 }
  15. 15
  16. 16 @Async("handleAsync")
  17. 17 public Future<String> handle2(int time) throws InterruptedException {
  18. 18 TimeUnit.SECONDS.sleep(time);
  19. 19 printCurrentTime("handle-2");
  20. 20 return new AsyncResult<>("result2");
  21. 21 }
  22. 22
  23. 23 @Async("handleAsync")
  24. 24 public Future<String> handle3(int time) throws InterruptedException {
  25. 25 TimeUnit.SECONDS.sleep(time);
  26. 26 printCurrentTime("handle-3");
  27. 27 return new AsyncResult<>("result3");
  28. 28 }
  29. 29
  30. 30
  31. 31 }

调用类

  1. 1 @Component
  2. 2 public class SimpleAsyncDemo {
  3. 3
  4. 4
  5. 5 @Autowired
  6. 6 private ReturnSuccTaskHandler returnSuccTaskHandler;
  7. 7
  8. 8
  9. 9
  10. 10 @PostConstruct
  11. 11 public void execTaskHandler4() {
  12. 12 try {
  13. 13 Future<String> a1 = returnSuccTaskHandler.handle1(2);
  14. 14 Future<String> a2 = returnSuccTaskHandler.handle2(2);
  15. 15 Future<String> a3 = returnSuccTaskHandler.handle3(2);
  16. 16 Future<String> a4 = returnSuccTaskHandler.handle1(2);
  17. 17 Future<String> a5 = returnSuccTaskHandler.handle2(2);
  18. 18 Future<String> a6 = returnSuccTaskHandler.handle3(2);
  19. 19 Future<String> a7 = returnSuccTaskHandler.handle1(2);
  20. 20 Future<String> a8 = returnSuccTaskHandler.handle2(2);
  21. 21 Future<String> a9 = returnSuccTaskHandler.handle3(2);
  22. 22 while (true){
  23. 23 // 如果任务都做完就执行如下逻辑
  24. 24 if (a1.isDone() &&
  25. 25 a2.isDone()&&
  26. 26 a3.isDone()&&
  27. 27 a4.isDone()&&
  28. 28 a5.isDone()&&
  29. 29 a6.isDone()&&
  30. 30 a7.isDone()&&
  31. 31 a8.isDone()&&
  32. 32 a9.isDone()){
  33. 33 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  34. 34 System.out.println(format.format(new Date()) + "async task end.");
  35. 35 System.out.println("async result:"+a1.get());
  36. 36 System.out.println("async result:"+a2.get());
  37. 37 System.out.println("async result:"+a3.get());
  38. 38 System.out.println("async result:"+a3.get());
  39. 39 System.out.println("async result:"+a4.get());
  40. 40 System.out.println("async result:"+a5.get());
  41. 41 System.out.println("async result:"+a6.get());
  42. 42 System.out.println("async result:"+a7.get());
  43. 43 System.out.println("async result:"+a8.get());
  44. 44 System.out.println("async result:"+a9.get());
  45. 45 break;
  46. 46 }
  47. 47 }
  48. 48 } catch (InterruptedException | ExecutionException e) {
  49. 49 e.printStackTrace();
  50. 50 }
  51. 51 }
  52. 52
  53. 53
  54. 54 }

输出结果如下,我们可以发现 ,1、可以拿到返回结果,2、在最后一个子任务执行完成后,即立刻拿到结果。

Java多线程开发系列之五:Springboot 中异步请求方法的使用的更多相关文章

  1. SpringBoot中异步请求和异步调用(看这一篇就够了)

    原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10661591.html,否则将追究法律责任!!! 一.SpringBoot中异步请求的使用 ...

  2. Java多线程开发系列之四:玩转多线程(线程的控制2)

    在上节的线程控制(详情点击这里)中,我们讲解了线程的等待join().守护线程.本节我们将会把剩下的线程控制内容一并讲完,主要内容有线程的睡眠.让步.优先级.挂起和恢复.停止等. 废话不多说,我们直接 ...

  3. Java多线程开发系列之番外篇:事件派发线程---EventDispatchThread

    事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点.今天我们在多线程开发中,穿插进来这个线程.分别从线程的来由.原理和使用方法三个方面来学习事件派发线程. 一 ...

  4. Java多线程开发系列之一:走进多线程

    对编程语言的基础知识:分支.选择.循环.面向对象等基本概念理解后,我们需要对java高级编程有一定的学习,这里不可避免的要接触到多线程开发. 由于多线程开发整体的系统比较大,我会写一个系列的文章总结介 ...

  5. Java多线程开发系列之二:如何创建多线程

    前文已介绍过多线程的基本知识了,比如什么是多线程,什么又是进程,为什么要使用多线程等等. 在了解了软件开发中使用多线程的基本常识后,我们今天来聊聊如何简单的使用多线程. 在Java中创建多线程的方式有 ...

  6. Java多线程开发系列之四:玩转多线程(线程的控制1)

    在前文中我们已经学习了:线程的基本情况.如何创建多线程.线程的生命周期.利用已有知识我们已经可以写出如何利用多线程处理大量任务这样简单的程序.但是当应用场景复杂时,我们还需要从管理控制入手,更好的操纵 ...

  7. Java多线程开发系列之三:线程这一辈子(线程的生命周期)

    前文中已经提到了,关于多线程的基础知识和多线程的创建.但是如果想要很好的管理多线程,一定要对线程的生命周期有一个整体概念.本节即对线程的一生进行介绍,让大家对线程的各个时段的状态有一定了解. 线程的一 ...

  8. Java多线程开发技巧

    很多开发者谈到Java多线程开发,仅仅停留在new Thread(...).start()或直接使用Executor框架这个层面,对于线程的管理和控制却不够深入,通过读<Java并发编程实践&g ...

  9. [转]Java多线程干货系列—(一)Java多线程基础

    Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...

随机推荐

  1. Codeforces Round #793 (Div. 2)

    C. LIS or Reverse LIS? D. Circular Spanning Tree E. Unordered Swaps F MCMF?

  2. CSS的几种选择器

    选择器 目录 选择器 基础选择器 标签选择器 类选择器 id选择器 通配符选择器 复合选择器 后代选择器 子选择器 并集选择器 伪类选择器 基础选择器 标签选择器 标签选择器可以把一类标签全部选择出来 ...

  3. 关于python导入数据库excel数据时出现102, b"Incorrect syntax near '.15562'.DB-Lib error message 20018, severity 1的问题总结

    1.对于在使用python导入sqlsever时,出现102, b"Incorrect syntax near '.15562'.DB-Lib error message 20018, se ...

  4. 『忘了再学』Shell基础 — 28、AWK中条件表达式说明

    目录 1.AWK的条件表达 2.条件表达式说明 (1)BEGIN (2)END (3)关系运算符 (4)说明AWK中条件表达式的执行过程 (5)AWK中使用正则表达式 (6)A~B练习 1.AWK的条 ...

  5. [自制操作系统] 第05回 CPU的三种模式

    目录 一.前景回顾 二.实模式和保护模式 一.前景回顾 在之前我们说到,loader的作用才是读取加载操作系统内核,那么我们的重心就应该是loader.S文件,其实我们接下来也的确是会往loader. ...

  6. 用python制作文件搜索工具,深挖电脑里的【学习大全】

    咳咳~懂得都懂啊 点击此处找管理员小姐姐领取正经资料~ 开发环境 解释器: Python 3.8.8 | Anaconda, Inc. 编辑器: pycharm 专业版 先演示效果 开始代码,先导入模 ...

  7. (数据科学学习手札140)详解geopandas中基于pyogrio的矢量读写引擎

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,前不久我在一篇文章中给大家分享 ...

  8. 小米社区APP深度体验

    小米社区APP深度体验 版本:3.0.210928 BUG 1,在暗黑模式下,会员一栏中的成就等级小字未作深色模式调整,从而造成文字难于识别. 2,在暗黑模式下,会员页中底部的会员产品首页视觉没有完美 ...

  9. java--运算符和表达式

    运算符:就是对常量或者遍历进行操作的符号: 表达式:用运算符把常量或者变量连接起来符合java语法的式子称为表达式,不同运算符连接的表达式体现的是不同类型的表达式. 一.算术运算符 1.使用%运算符: ...

  10. 看起来是线程池的BUG,但是我认为是源码设计不合理。

    你好呀,我是歪歪. 前几天看到一个 JDK 线程池的 BUG,我去了解了一下,摸清楚了它的症结所在之后,我觉得这个 BUG 是属于一种线程池方法设计不合理的地方,而且官方在知道这个 BUG 之后表示: ...