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


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

    启动类

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

    调用类

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

    被异步调用的类

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

     

    执行结果

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

    默认配置如下

    复制代码
    # 核心线程数
    spring.task.execution.pool.core-size=8  
    # 最大线程数
    spring.task.execution.pool.max-size=16
    # 空闲线程存活时间
    spring.task.execution.pool.keep-alive=60s
    # 是否允许核心线程超时
    spring.task.execution.pool.allow-core-thread-timeout=true
    # 线程队列数量
    spring.task.execution.pool.queue-capacity=100
    # 线程关闭等待
    spring.task.execution.shutdown.await-termination=false
    spring.task.execution.shutdown.await-termination-period=
    # 线程名称前缀
    spring.task.execution.thread-name-prefix=task-
    复制代码

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

    application.properties配置文件

    task.pool.demo.corePoolSize= 5
    task.pool.demo.maxPoolSize= 10
    task.pool.demo.keepAliveSeconds= 300
    task.pool.demo.queueCapacity= 50

     

    调用类

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

     

    异步线程池的配置类

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

     

    被异步调用的类

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

    执行结果如下

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

    调用类

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

    被调用类

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

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

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

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

    调用类

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

     

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

     

  • 相关阅读:
    微服务全链路监控中Trace和Span介绍
    集线器-交换机-路由器
    Docker
    开源一个RAG大模型本地知识库问答机器人-ChatWiki
    Cpp浅析系列-STL之deque
    使用Rancher搭建Kubernetes集群
    网站在百度站长平台无上传站点LOGO权限下,如何实现LOGO图片在百度索引中自动抓取展现?
    Vue中如何获取dom元素?
    在Mac m1运行ChatGLM3-6B cpu版本1-3秒出结果,速度明显超过T4 GPU,接近V100。
    Pytorch 图像增强 实现翻转裁剪色调等 附代码(全)
  • 原文地址:https://www.cnblogs.com/jilodream/p/16592604.html