Spring中異步方法的使用
1. 異步方法描述
異步方法,顧名思義就是調用後無須等待它的執行,而繼續往下執行;@Async是Spring的一個註解,在Spring Boot中,我們只需要使用@Async註解就能簡單的將原來的同步函數變爲異步函數。
對於比較耗時的操作,我們可以抽取成異步方法來讓主線程穩定快速繼續執行,對於異步方法的執行結果可根據自己的要求是否需要在主線程處理;
2. 異步方法的實現步驟
在springboot應用我們可以使用簡單的兩個註解即可開啓並使用異步方法;
- 創建一個普通的Service類,並有@Service修飾,表示這個服務類交給Spring管理;
- 在Service方法裏定義一個普通的方法,使用@Async修飾;表示這是一個異步方法;
- 在引導類中添加@EnableAsync註解,使應用開啓對異步方法的支持;
3. 實測一下
3.1 定義個Service類
@Slf4j
@Service
public class MyAsyncSevice {
@Async
public void myAsyncMehtod(){
log.info("---> enter aysnc method");
try {
Thread.sleep(5000);
int i = 1/0;
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("---> end of async method");
}
}
3.2 定義個測試Controlle
@Slf4j
@RestController
public class AsyncContorller {
// 注入異步方法所在的類
@Autowired
MyAsyncSevice myAsyncSevice;
@GetMapping("/async-test")
public String asyncTest(){
log.info("--> enter controller");
myAsyncSevice.myAsyncMehtod();
log.info("--> end of controller");
return "hello";
}
}
3.3 在啓動類上啓用異步
@EnableAsync
@SpringBootApplication
public class JsrDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JsrDemoApplication.class, args);
}
}
3.4 測試
可以看到輸出,controlle中調用了異步方法後繼續執行,即使異步方法報錯也不會controller的執行;
curl -X GET -H "http://localhost:8080/async-test"
2021-08-20 17:42:50.810 --- [nio-8080-exec-1] AsyncContorller : --> enter controller
2021-08-20 17:42:50.813 --- [nio-8080-exec-1] AsyncContorller : --> end of controller
2021-08-20 17:42:50.821 --- [ task-1] MyAsyncSevice : ---> enter aysnc method
2021-08-20 17:42:55.832 --- [ task-1] MyAsyncSevice : ---> end of async method
4. 補充
4.1 Executor線程池
上面的測試,我們並沒有創建新的線程和線程池,如果我們不配置線程池的Bean,Spring會自動創建SimpleAsyncTaskExecutor,並使用它來執行異步方法。定義線程池bean可參考如下:
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("MyThreadPool");
executor.initialize();
return executor;
}
4.2 異步返回Futrue
4.2.1 這樣改造我們的異步方法,即返回一個Futrue類型的結果;
@Async
public Future myAsyncMehtod(){
log.info("---> enter aysnc method");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("---> end of async method");
return new AsyncResult("I am async method result");
}
4.2.2 Contorller也改造下
使用了Futrue.get方法,controller主線程就會等待異步方法的執行結束,或等待超時後纔會結束。
@GetMapping("/async-test")
public String asyncTest(){
log.info("--> enter controller");
Future ft = myAsyncSevice.myAsyncMehtod();
try {
ft.get(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
log.info("--> end of controller");
return "hello";
}
4.3 其他注意事項
- 在同一個類中的方法調用,添加@async註解是失效的。原因是當你在同一個類中的時候,方法調用是在類中執行的,spring無法截獲這個方法調用,也就不會在代理類裏執行。
- 可能會導致循環依賴,spring本身會解決循環依賴,但是因爲@Async使用代理模式,spring在檢查第二級緩存和原始對象是否相等時發現不相等,會拋出異常。
- 無法獲取請求上下文。