一文搞懂SpringBoot如何利用@Async實現(xiàn)異步調(diào)用
前言
異步調(diào)用幾乎是處理高并發(fā),解決性能問題常用的手段,如何開啟異步調(diào)用?SpringBoot中提供了非常簡單的方式,就是一個注解@Async。今天我們重新認識一下@Async,以及注意事項
簡單使用
新建三個作業(yè)任務:
@Service
public class TaskDemo {
private static Logger logger = LoggerFactory.getLogger(TaskDemo.class);
public void execute1() {
logger.info("處理耗時任務1......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務1......結束");
}
public void execute2() {
logger.info("處理耗時任務2......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務2......結束");
}
public void execute3() {
logger.info("處理耗時任務3......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務3......結束");
}
}測試代碼:
@RestController
public class TaskController {
@Autowired
private TaskDemo taskDemo;
@GetMapping("/task/test")
public String testTask() {
taskDemo.execute1();
taskDemo.execute2();
taskDemo.execute3();
return "ok";
}
}執(zhí)行后我們可以發(fā)現(xiàn),上面的代碼是同一個線程的同步執(zhí)行,整體耗時9秒才完成。

異步處理
springboot的異步,是非常簡單的,加2個注解即可
@Service
public class TaskDemo {
private static Logger logger = LoggerFactory.getLogger(TaskDemo.class);
@Async
public void execute1() {
logger.info("處理耗時任務1......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務1......結束");
}
@Async
public void execute2() {
logger.info("處理耗時任務2......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務2......結束");
}
@Async
public void execute3() {
logger.info("處理耗時任務3......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務3......結束");
}
}@SpringBootApplication
@EnableAsync
public class DemoApp {
public static void main(String[] args) {
SpringApplication.run(DemoApp.class,args);
}
}增加了@Async和@EnableAsync兩個注解

從執(zhí)行結果發(fā)現(xiàn),整個流程用了3秒,以及用了3個線程執(zhí)行。完成了異步調(diào)用
異步回調(diào)
有些場景我們需要知道異步處理的任務什么時候完成,需要做額外的業(yè)務處理。如:我們需要在3個任務都完成后,提示一下給用戶
@Service
public class TaskDemo {
private static Logger logger = LoggerFactory.getLogger(TaskDemo.class);
@Async
public Future<String> execute1() {
logger.info("處理耗時任務1......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務1......結束");
return new AsyncResult<>("任務1 ok");
}
@Async
public Future<String> execute2() {
logger.info("處理耗時任務2......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務2......結束");
return new AsyncResult<>("任務2 ok");
}
@Async
public Future<String> execute3() {
logger.info("處理耗時任務3......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務3......結束");
return new AsyncResult<>("任務3 ok");
}
}@RestController
public class TaskController {
private static Logger logger = LoggerFactory.getLogger(TaskController.class);
@Autowired
private TaskDemo taskDemo;
@GetMapping("/task/test")
public String testTask() throws InterruptedException {
Future<String> task1 = taskDemo.execute1();
Future<String> task2 = taskDemo.execute2();
Future<String> task3 = taskDemo.execute3();
while (true){
if (task1.isDone() && task2.isDone() && task3.isDone()){
break;
}
TimeUnit.SECONDS.sleep(1);
}
logger.info(">>>>>>3個任務都處理完成");
return "ok";
}
}
執(zhí)行結果發(fā)現(xiàn),在請求線程里面給用戶提示了3個任務都處理完成了。
這段代碼主要改變了什么:
1、把具體任務返回類型改為了Future類型對象
2、在調(diào)用任務時,循環(huán)判斷任務是否處理完
自定義線程池
說到異步處理,一定要考慮到線程池,什么是線程池,小伙伴可自行網(wǎng)補。@Async的線程池定義比較方便,直接上代碼:
@Configuration
public class ThreadPoolConfig {
@Bean(name = "taskPool01Executor")
public ThreadPoolTaskExecutor getTaskPool01Executor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心線程數(shù)
taskExecutor.setCorePoolSize(10);
//線程池維護線程的最大數(shù)量,只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程
taskExecutor.setMaxPoolSize(100);
//緩存隊列
taskExecutor.setQueueCapacity(50);
//許的空閑時間,當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀
taskExecutor.setKeepAliveSeconds(200);
//異步方法內(nèi)部線程名稱
taskExecutor.setThreadNamePrefix("TaskPool-01-");
/**
* 當線程池的任務緩存隊列已滿并且線程池中的線程數(shù)目達到maximumPoolSize,如果還有任務到來就會采取任務拒絕策略
* 通常有以下四種策略:
* ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
* ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
* ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執(zhí)行任務(重復此過程)
* ThreadPoolExecutor.CallerRunsPolicy:重試添加當前的任務,自動重復調(diào)用 execute() 方法,直到成功
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.initialize();
return taskExecutor;
}
@Bean(name = "taskPool02Executor")
public ThreadPoolTaskExecutor getTaskPool02Executor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心線程數(shù)
taskExecutor.setCorePoolSize(10);
//線程池維護線程的最大數(shù)量,只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程
taskExecutor.setMaxPoolSize(100);
//緩存隊列
taskExecutor.setQueueCapacity(50);
//許的空閑時間,當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀
taskExecutor.setKeepAliveSeconds(200);
//異步方法內(nèi)部線程名稱
taskExecutor.setThreadNamePrefix("TaskPool-02-");
/**
* 當線程池的任務緩存隊列已滿并且線程池中的線程數(shù)目達到maximumPoolSize,如果還有任務到來就會采取任務拒絕策略
* 通常有以下四種策略:
* ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
* ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
* ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執(zhí)行任務(重復此過程)
* ThreadPoolExecutor.CallerRunsPolicy:重試添加當前的任務,自動重復調(diào)用 execute() 方法,直到成功
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.initialize();
return taskExecutor;
}
}定義了2個線程池Bean
@Service
public class TaskDemo {
private static Logger logger = LoggerFactory.getLogger(TaskDemo.class);
@Async("taskPool01Executor")
public Future<String> execute1() {
logger.info("處理耗時任務1......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務1......結束");
return new AsyncResult<>("任務1 ok");
}
@Async("taskPool01Executor")
public Future<String> execute2() {
logger.info("處理耗時任務2......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務2......結束");
return new AsyncResult<>("任務2 ok");
}
@Async("taskPool02Executor")
public Future<String> execute3() {
logger.info("處理耗時任務3......開始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("處理耗時任務3......結束");
return new AsyncResult<>("任務3 ok");
}
}@Async(“線程池名稱”),指定value使用自己定義的線程池:

執(zhí)行結果利用了線程池。
注意事項(一定注意)
在使用@Async注解時,很多小伙伴都會發(fā)現(xiàn)異步使用失敗。主要原因是異步方法的定義出了問題。
1、異步方法不能使用static修飾
2、異步類沒有使用@Component注解(或其他注解)導致spring無法掃描到異步類
3、異步方法和調(diào)用異步方法的方法不能在同一個類
4、類中需要使用@Autowired或@Resource等注解自動注入,不能自己手動new對象
5、如果使用SpringBoot框架必須在啟動類中增加@EnableAsync注解?
到此這篇關于一文搞懂SpringBoot如何利用@Async實現(xiàn)異步調(diào)用的文章就介紹到這了,更多相關SpringBoot @Async異步調(diào)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
spring?aop?Pointcut?execution規(guī)則介紹
這篇文章主要介紹了spring?aop?Pointcut?execution規(guī)則,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot項目攔截器獲取Post方法的請求body實現(xiàn)
本文主要介紹了SpringBoot項目攔截器獲取Post方法的請求body,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Java中SimpleDateFormat 格式化日期的使用
本文主要介紹了Java中SimpleDateFormat 格式化日期的使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
SpringBoot指標監(jiān)控功能實現(xiàn)
這篇文章主要介紹了SpringBoot指標監(jiān)控功能實現(xiàn),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06
淺談Spring中幾個PostProcessor的區(qū)別與聯(lián)系
這篇文章主要介紹了淺談Spring中幾個PostProcessor的區(qū)別與聯(lián)系,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Java利用數(shù)組隨機抽取幸運觀眾如何實現(xiàn)
這篇文章主要介紹了Java利用數(shù)組隨機抽取幸運觀眾如何實現(xiàn),需要的朋友可以參考下2014-02-02
springboot中json對象中對Long類型和String類型相互轉(zhuǎn)換
與前端聯(lián)調(diào)接口時,后端一些字段設計為Long類型,這樣就有可能導致前端缺失精度,這時候我們就需要將Long類型返回給前端時做數(shù)據(jù)類型轉(zhuǎn)換,本文主要介紹了springboot中json對象中對Long類型和String類型相互轉(zhuǎn)換,感興趣的可以了解一下2023-11-11

