SpringBoot异步方法支持注解@Async应用
1.为什么需要异步方法?
合理使用异步方法可以有效的提高执行效率
同步执行(同在一个线程中):
异步执行(开启额外线程来执行):
2.SpringBoot中的异步方法支持
在SpringBoot中并不需要我们自己去创建维护线程或者线程池来异步的执行方法, SpringBoot已经提供了异步方法支持注解.
java
@EnableAsync // 使用异步方法时需要提前开启(在启动类上或配置类上)
@Async // 被async注解修饰的方法由SpringBoot默认线程池(SimpleAsyncTaskExecutor)执行
service层:
java
@Service
public class ArticleServiceImpl {
// 查询文章
public String selectArticle() {
// 模拟文章查询操作
System.out.println("查询任务线程"+Thread.currentThread().getName());
return "文章详情";
}
// 文章阅读量+1
@Async
public void updateReadCount() {
try {
// 模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新任务线程"+Thread.currentThread().getName());
}
}
controller层:
java
@RestController
public class AsyncTestController {
@Autowired
private ArticleServiceImpl articleService;
/**
* 模拟获取文章后阅读量+1
*/
@GetMapping("/article")
public String getArticle() {
long start = System.currentTimeMillis();
// 查询文章
String article = articleService.selectArticle();
// 阅读量+1
articleService.updateReadCount();
long end = System.currentTimeMillis();
System.out.println("文章阅读业务执行完毕,执行共计耗时:"+(end-start));
return article;
}
}
测试结果: 我们可以感受到接口响应速度大大提升, 而且从日志中key看到两个执行任务是在不同的线程中执行的
java
查询任务线程http-nio-8800-exec-3
文章阅读业务执行完毕,执行共计耗时:56
更新任务线程task-1
3.自定义线程池执行异步方法
SpringBoot为我们默认提供了线程池(SimpleAsyncTaskExecutor)来执行我们的异步方法, 我们也可以自定义自己的线程池.
第一步配置自定义线程池
java
@EnableAsync // 开启多线程, 项目启动时自动创建
@Configuration
public class AsyncConfig {
@Bean("customExecutor")
public ThreadPoolTaskExecutor asyncOperationExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(8);
// 设置最大线程数
executor.setMaxPoolSize(20);
// 设置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置线程名前缀+分组名称
executor.setThreadNamePrefix("AsyncOperationThread-");
executor.setThreadGroupName("AsyncOperationGroup");
// 所有任务结束后关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 初始化
executor.initialize();
return executor;
}
}
第二步, 在@Async注解上指定执行的线程池即可
java
// 文章阅读量+1
@Async("customExecutor")
public void updateReadCount() {
// TODO 模拟耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新文章阅读量线程"+Thread.currentThread().getName());
}
测试结果:
java
查询任务线程http-nio-8800-exec-1
文章阅读业务执行完毕,执行共计耗时:17
更新任务线程AsyncOperationThread-1
4.如何捕获(无返回值的)异步方法中的异常
以实现AsyncConfigurer接口的getAsyncExecutor方法和getAsyncUncaughtExceptionHandler方法改造配置类
自定义异常处理类CustomAsyncExceptionHandler
java
@EnableAsync // 开启多线程, 项目启动时自动创建
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(8);
// 设置最大线程数
executor.setMaxPoolSize(20);
// 设置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置线程名前缀+分组名称
executor.setThreadNamePrefix("AsyncOperationThread-");
executor.setThreadGroupName("AsyncOperationGroup");
// 所有任务结束后关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 初始化
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
System.out.println("异常捕获---------------------------------");
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : obj) {
System.out.println("Parameter value - " + param);
}
System.out.println("异常捕获---------------------------------");
}
}
测试结果:
java
查询任务线程http-nio-8800-exec-1
文章阅读业务执行完毕,执行共计耗时:20
异常捕获---------------------------------
Exception message - / by zero
Method name - updateReadCount
异常捕获---------------------------------
5.如何获取(有返回值)异步方法的返回值
使用Future类及其子类来接收异步方法返回值
注意:
- 无返回值的异步方法抛出异常不会影响Controller的主要业务逻辑
- 有返回值的异步方法抛出异常会影响Controller的主要业务逻辑
java
// 异步方法---------------------------------------------------------------------
@Async
public CompletableFuture<Integer> updateReadCountHasResult() {
try {
// 模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新文章阅读量线程"+Thread.currentThread().getName());
return CompletableFuture.completedFuture(100 + 1);
}
// Controller调用---------------------------------------------------------------------
@GetMapping("/article")
public String getArticle() throws ExecutionException, InterruptedException {
// 查询文章
String article = articleService.selectArticle();
// 阅读量+1
CompletableFuture<Integer> future = articleService.updateReadCountHasResult();
int count = 0;
// 循环等待异步请求结果
while (true) {
if(future.isCancelled()) {
System.out.println("异步任务取消");
break;
}
if (future.isDone()) {
count = future.get();
System.out.println(count);
break;
}
}
System.out.println("文章阅读业务执行完毕");
return article + count;
}
6.常见失效场景
- 主启动类或者配置类没有添加@EnableAsync注解
- A方法调用被@Async注解修饰的B方法
java
@RestController
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/getAll")
public void getUsers(){
System.out.println("业务开始");
test();
System.out.println("业务结束");
}
@Async
public void test(){
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"查询到了所有的用户信息!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
- 被@Async注解修饰的方法必须不可以是static和private,必须为public
- 需要通过@Autowired或@Resource进行注入,不可手动new,基于SpringAOP实现