Java多线程开发系列之五:Springboot 中异步请求方法的使用 ...

打印 上一主题 下一主题

主题 862|帖子 862|积分 2586

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、在最后一个子任务执行完成后,即立刻拿到结果。

 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

徐锦洪

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表