一、异步
异步方法适用于逻辑与逻辑之间可以相互分割互不影响的业务中, 如生成验证码和发送验证码组成的业务, 其实无需等到真正发送成功验证码才对客户端进行响应, 可以让短信发送这一耗时操作转为异步执行, 解耦耗时操作和核心业务。
有个业务场景,业务数据审核通过后需要给用户发短信,发短信过程比较耗时,可能需要几秒甚至十几秒,因此使用异步发短信。使用注解@Async来实现。
二、SpringBoot中的异步方法
在SpringBoot中并不需要我们自己去创建维护线程或者线程池来异步的执行方法, SpringBoot已经提供了异步方法支持注解。
@EnableAsync // 使用异步方法时需要提前开启(在启动类上或配置类上)
@Async // 被async注解修饰的方法由SpringBoot默认线程池(SimpleAsyncTaskExecutor)执行
主启动类SpringbootApplication
@SpringBootApplication
@EnableAsync
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
ArticleService
@Service
public class ArticleService {
// 查询文章
public String selectArticle() {
// TODO 模拟文章查询操作
System.out.println("查询任务线程,线程名:"+Thread.currentThread().getName());
return "文章详情";
}
// 文章阅读量+1
@Async
public void updateReadCount() {
// TODO 模拟耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新任务线程,线程名:"+Thread.currentThread().getName());
}
}
AsyncArcicleController
@RestController
public class AsyncArcicleController {
@Autowired
private ArticleService articleService;
/**
* 模拟获取文章后阅读量+1
*/
@PostMapping("/article")
public String getArticle() {
// 查询文章
String article = articleService.selectArticle();
// 阅读量+1
articleService.updateReadCount();
System.out.println("getArticle文章阅读业务执行完毕");
return article;
}
}
利用PostMan测试http://localhost:8080/article
三、自定义线程池执行异步方法
SpringBoot为我们默认提供了线程池(SimpleAsyncTaskExecutor)来执行我们的异步方法, 我们也可以自定义自己的线程池.
第一步配置自定义线程池
AsyncConfig
@EnableAsync // 开启多线程, 项目启动时自动创建
@Configuration
public class AsyncConfig {
@Bean("readCountExecutor") //指定自定义线程池名称
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注解上指定执行的线程池,ArticleService中指定执行的线程池。
// 文章阅读量+1,指定线程池
@Async("readCountExecutor")
public void updateReadCountByExecutor() {
// TODO 模拟耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新任务线程,线程名:"+Thread.currentThread().getName());
}
第三步,在AsyncArcicleController中
/**
* 模拟获取文章后阅读量+1,指定线程池
*/
@PostMapping("/articleByExecutor")
public String getArticleByExecutor() {
// 查询文章
String article = articleService.selectArticle();
// 阅读量+1
articleService.updateReadCountByExecutor();
System.out.println("getArticleByExecutor文章阅读业务执行完毕");
return article;
}
测试结果
四、捕获(无返回值的)异步方法中的异常
以实现AsyncConfigurer接口的getAsyncExecutor方法和getAsyncUncaughtExceptionHandler方法改造配置类
自定义异常处理类CustomAsyncExceptionHandler
@Component
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
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("异常捕获---------------------------------");
}
}
ArticleService中updateReadCountNoReturnByExecutor故意设置执行异常
// 文章阅读量+1,指定线程池
@Async("readCountNoReturnExecutor")
public void updateReadCountNoReturnByExecutor() {
// TODO 模拟耗时操作
try {
Thread.sleep(3000);
int i = 1/0;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新任务线程,线程名:"+Thread.currentThread().getName());
}
测试结果
五、获取(有返回值)异步方法的返回值
使用Future类及其子类来接收异步方法返回值
注意:
无返回值的异步方法抛出异常不会影响Controller的主要业务逻辑
有返回值的异步方法抛出异常会影响Controller的主要业务逻辑
ArticleService中updateReadCountHasResult
// 文章阅读量+1
@Async("readCountExecutor")
public CompletableFuture<Integer> updateReadCountHasResult() {
// TODO 模拟耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新文章阅读量线程"+Thread.currentThread().getName());
return CompletableFuture.completedFuture(100 + 1);
}
在AsyncArcicleController中
@GetMapping("/articleCompletableFuture")
public String getArticleCompletableFuture() throws ExecutionException, InterruptedException {
// 查询文章
String article = articleService.selectArticle();
// 阅读量+1
CompletableFuture<Integer> future = articleService.updateReadCountHasResult();
//无返回值的异步方法抛出异常不会影响Controller的主要业务逻辑
//有返回值的异步方法抛出异常会影响Controller的主要业务逻辑
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("getArticleCompletableFuture文章阅读业务执行完毕");
return article + count;
}
测试结果
六、项目整体结构和代码
代码地址
下载
参考文章
https://mp.weixin.qq.com/s/CJt0fHy2bUgteevlefEy-g
https://www.jianshu.com/p/d919f4372351
https://www.cnblogs.com/ph7seven/p/9549824.html