SpringBoot異步方法支持注解@Async應(yīng)用
1.為什么需要異步方法?
合理使用異步方法可以有效的提高執(zhí)行效率
同步執(zhí)行(同在一個(gè)線程中):
異步執(zhí)行(開啟額外線程來執(zhí)行):
2.SpringBoot中的異步方法支持
在SpringBoot中并不需要我們自己去創(chuàng)建維護(hù)線程或者線程池來異步的執(zhí)行方法, SpringBoot已經(jīng)提供了異步方法支持注解.
@EnableAsync // 使用異步方法時(shí)需要提前開啟(在啟動(dòng)類上或配置類上)
@Async // 被async注解修飾的方法由SpringBoot默認(rèn)線程池(SimpleAsyncTaskExecutor)執(zhí)行
service層:
@Service
public class ArticleServiceImpl {
// 查詢文章
public String selectArticle() {
// 模擬文章查詢操作
System.out.println("查詢?nèi)蝿?wù)線程"+Thread.currentThread().getName());
return "文章詳情";
}
// 文章閱讀量+1
@Async
public void updateReadCount() {
try {
// 模擬耗時(shí)操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新任務(wù)線程"+Thread.currentThread().getName());
}
}
controller層:
@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("文章閱讀業(yè)務(wù)執(zhí)行完畢,執(zhí)行共計(jì)耗時(shí):"+(end-start));
return article;
}
}
測(cè)試結(jié)果: 我們可以感受到接口響應(yīng)速度大大提升, 而且從日志中key看到兩個(gè)執(zhí)行任務(wù)是在不同的線程中執(zhí)行的
查詢?nèi)蝿?wù)線程http-nio-8800-exec-3
文章閱讀業(yè)務(wù)執(zhí)行完畢,執(zhí)行共計(jì)耗時(shí):56
更新任務(wù)線程task-1
3.自定義線程池執(zhí)行異步方法
SpringBoot為我們默認(rèn)提供了線程池(SimpleAsyncTaskExecutor)來執(zhí)行我們的異步方法, 我們也可以自定義自己的線程池.
第一步配置自定義線程池
@EnableAsync // 開啟多線程, 項(xiàng)目啟動(dòng)時(shí)自動(dòng)創(chuàng)建
@Configuration
public class AsyncConfig {
@Bean("customExecutor")
public ThreadPoolTaskExecutor asyncOperationExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 設(shè)置核心線程數(shù)
executor.setCorePoolSize(8);
// 設(shè)置最大線程數(shù)
executor.setMaxPoolSize(20);
// 設(shè)置隊(duì)列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 設(shè)置線程活躍時(shí)間(秒)
executor.setKeepAliveSeconds(60);
// 設(shè)置線程名前綴+分組名稱
executor.setThreadNamePrefix("AsyncOperationThread-");
executor.setThreadGroupName("AsyncOperationGroup");
// 所有任務(wù)結(jié)束后關(guān)閉線程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 初始化
executor.initialize();
return executor;
}
}
第二步, 在@Async注解上指定執(zhí)行的線程池即可
// 文章閱讀量+1
@Async("customExecutor")
public void updateReadCount() {
// TODO 模擬耗時(shí)操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新文章閱讀量線程"+Thread.currentThread().getName());
}
測(cè)試結(jié)果:
查詢?nèi)蝿?wù)線程http-nio-8800-exec-1
文章閱讀業(yè)務(wù)執(zhí)行完畢,執(zhí)行共計(jì)耗時(shí):17
更新任務(wù)線程AsyncOperationThread-1
4.如何捕獲(無返回值的)異步方法中的異常
以實(shí)現(xiàn)AsyncConfigurer接口的getAsyncExecutor方法和getAsyncUncaughtExceptionHandler方法改造配置類
自定義異常處理類CustomAsyncExceptionHandler
@EnableAsync // 開啟多線程, 項(xiàng)目啟動(dòng)時(shí)自動(dòng)創(chuàng)建
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 設(shè)置核心線程數(shù)
executor.setCorePoolSize(8);
// 設(shè)置最大線程數(shù)
executor.setMaxPoolSize(20);
// 設(shè)置隊(duì)列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 設(shè)置線程活躍時(shí)間(秒)
executor.setKeepAliveSeconds(60);
// 設(shè)置線程名前綴+分組名稱
executor.setThreadNamePrefix("AsyncOperationThread-");
executor.setThreadGroupName("AsyncOperationGroup");
// 所有任務(wù)結(jié)束后關(guān)閉線程池
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("異常捕獲---------------------------------");
}
}
測(cè)試結(jié)果:
查詢?nèi)蝿?wù)線程http-nio-8800-exec-1
文章閱讀業(yè)務(wù)執(zhí)行完畢,執(zhí)行共計(jì)耗時(shí):20
異常捕獲---------------------------------
Exception message - / by zero
Method name - updateReadCount
異常捕獲---------------------------------
5.如何獲取(有返回值)異步方法的返回值
使用Future類及其子類來接收異步方法返回值文章來源:http://www.zghlxwxcb.cn/news/detail-680053.html
注意:文章來源地址http://www.zghlxwxcb.cn/news/detail-680053.html
- 無返回值的異步方法拋出異常不會(huì)影響Controller的主要業(yè)務(wù)邏輯
- 有返回值的異步方法拋出異常會(huì)影響Controller的主要業(yè)務(wù)邏輯
// 異步方法---------------------------------------------------------------------
@Async
public CompletableFuture<Integer> updateReadCountHasResult() {
try {
// 模擬耗時(shí)操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("更新文章閱讀量線程"+Thread.currentThread().getName());
return CompletableFuture.completedFuture(100 + 1);
}
// Controller調(diào)用---------------------------------------------------------------------
@GetMapping("/article")
public String getArticle() throws ExecutionException, InterruptedException {
// 查詢文章
String article = articleService.selectArticle();
// 閱讀量+1
CompletableFuture<Integer> future = articleService.updateReadCountHasResult();
int count = 0;
// 循環(huán)等待異步請(qǐng)求結(jié)果
while (true) {
if(future.isCancelled()) {
System.out.println("異步任務(wù)取消");
break;
}
if (future.isDone()) {
count = future.get();
System.out.println(count);
break;
}
}
System.out.println("文章閱讀業(yè)務(wù)執(zhí)行完畢");
return article + count;
}
6.常見失效場(chǎng)景
- 主啟動(dòng)類或者配置類沒有添加@EnableAsync注解
- A方法調(diào)用被@Async注解修飾的B方法
@RestController
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/getAll")
public void getUsers(){
System.out.println("業(yè)務(wù)開始");
test();
System.out.println("業(yè)務(wù)結(jié)束");
}
@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進(jìn)行注入,不可手動(dòng)new,基于SpringAOP實(shí)現(xiàn)
到了這里,關(guān)于SpringBoot異步方法支持注解@Async應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!