SpringBoot异步编程
异步调用:当我们执行一个方法时,假如这个方法中有多个耗时的任务需要同时去做,而且又不着急等待这个结果时可以让客户端立即返回然后,后台慢慢去计算任务。当然你也可以选择等这些任务都执行完了,再返回给客户端。
SpringBoot 异步编程实战
如果我们需要在 SpringBoot 实现异步编程的话,通过 Spring 提供的两个注解会让这件事情变的非常简单。
@EnableAsync
:通过在配置类或者Main类上加@EnableAsync开启对异步方法的支持。@Async
可以作用在类上或者方法上,作用在类上代表这个类的所有方法都是异步方法。
1. 自定义 TaskExecutor
很多人对于 TaskExecutor 不是太了解,所以我们花一点篇幅先介绍一下这个东西。从名字就能看出它是任务的执行者,它领导执行着线程来处理任务,就像司令官一样,而我们的线程就好比一只只军队一样,这些军队可以异步对敌人进行打击👊。
Spring 提供了TaskExecutor
接口作为任务执行者的抽象,它和java.util.concurrent
包下的Executor
接口很像。稍微不同的 TaskExecutor
接口用到了 Java 8 的语法@FunctionalInterface
声明这个接口是一个函数式接口。
如果没有自定义Executor, Spring 将创建一个 SimpleAsyncTaskExecutor
并使用它。
1 package com.test.configure; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.scheduling.annotation.AsyncConfigurer; 6 import org.springframework.scheduling.annotation.EnableAsync; 7 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 9 import java.util.concurrent.Executor; 10 import java.util.concurrent.ThreadPoolExecutor; 11 12 @Configuration 13 @EnableAsync 14 public class AsyncConfig implements AsyncConfigurer { 15 16 private static final int CORE_POOL_SIZE = 6; 17 private static final int MAX_POOL_SIZE = 10; 18 private static final int QUEUE_CAPACITY = 100; 19 20 @Bean 21 public Executor taskExecutor() { 22 // Spring 默认配置是核心线程数大小为1,最大线程容量大小不受限制,队列容量也不受限制。 23 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 24 // 核心线程数 25 executor.setCorePoolSize(CORE_POOL_SIZE); 26 // 最大线程数 27 executor.setMaxPoolSize(MAX_POOL_SIZE); 28 // 队列大小 29 executor.setQueueCapacity(QUEUE_CAPACITY); 30 // 当最大池已满时,此策略保证不会丢失任务请求,但是可能会影响应用程序整体性能。 31 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 32 executor.setThreadNamePrefix("My ThreadPoolTaskExecutor-"); 33 executor.initialize(); 34 return executor; 35 } 36 }
Spring 默认使用的是 ThreadPoolExecutor.AbortPolicy
。在Spring的默认情况下,ThreadPoolExecutor
将抛出 RejectedExecutionException
来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy
。当最大池被填满时,此策略为我们提供可伸缩队列。
ThreadPoolTaskExecutor
饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量时,ThreadPoolTaskExecutor
定义一些策略:
- ThreadPoolExecutor.AbortPolicy:抛出
RejectedExecutionException
来拒绝新任务的处理。 - ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
2. 编写一个异步的方法
1 package com.test.service; 2 3 import org.springframework.scheduling.annotation.Async; 4 import org.springframework.stereotype.Service; 5 6 import java.text.SimpleDateFormat; 7 import java.util.Date; 8 9 @Service 10 public class AsyncService { 11 12 @Async 13 public void test() { 14 SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss"); 15 String start = sdf.format(new Date()); 16 try { 17 Thread.sleep(3000); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("startTime --->" +start + "EndTime --->" + sdf.format(new Date())); 22 } 23 }
3. 测试编写的异步方法
1 package com.test.async.controller; 2 3 import com.test.service.AsyncService; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.scheduling.annotation.Async; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RestController; 8 9 import java.util.HashMap; 10 11 @RestController 12 @RequestMapping(value = "/async") 13 public class AsyncController { 14 15 @Autowired 16 private AsyncService asyncService; 17 18 19 @RequestMapping(value = "/test") 20 public HashMap<String,String> test() { 21 HashMap<String,String> map = new HashMap<>(); 22 map.put("name","libai"); 23 asyncService.test(); 24 return map; 25 } 26 }
测试结果:
startTime --->2019-10-28 16:34:48 EndTime --->2019-10-28 16:34:51 startTime --->2019-10-28 16:34:48 EndTime --->2019-10-28 16:34:51 startTime --->2019-10-28 16:34:48 EndTime --->2019-10-28 16:34:51 startTime --->2019-10-28 16:34:57 EndTime --->2019-10-28 16:35:00 startTime --->2019-10-28 16:34:57 EndTime --->2019-10-28 16:35:00
请求会立即返回,而控制台的输出会在3秒之后输出。