如何在項目中使用Spring非同步調用註解@Async
- 2019 年 10 月 25 日
- 筆記
本文主要介紹如何使用Spring框架提供的非同步調用註解@Async,非同步執行緒池配置、異常捕獲處理。
開啟@Async註解支援
使用@Async註解的之前,必須在項目中啟動時調用@EnableAsync註解。比如通過定義一個JavaConfig文件:
@Configuration @EnableAsync public class AsyncConfig { }
非同步調用
使用@Async非同步執行無返回值的任務
定義一個任務類AsyncTask,包含兩個執行耗時任務的方法task1()、task2(),在兩個方法上添加@Async
@Service @Slf4j public class AsyncTask { @Async public void task1() { log.info("task1 start"); } @Async public void task2() { log.info("task2 start"); } }
定義測試類,串列調用AsyncTask.task1()和AsyncTask.task2()
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; @Test public void taskTest() { log.info("taskTest start"); asyncTask.task1(); asyncTask.task2(); log.info("taskTest end"); } }
從運行結果中看,task1和task2分別在兩個不同的執行緒中執行:
INFO [15:18:29.182][main][com.breezek.demo.common.AsyncTaskTest][25]:taskTest start INFO [15:18:29.188][main][com.breezek.demo.common.AsyncTaskTest][29]:taskTest end INFO [15:18:29.192][task-1][com.breezek.demo.common.AsyncTask][29]:task2 start INFO [15:18:29.192][task-2][com.breezek.demo.common.AsyncTask][24]:task1 start
非同步回調
使用@Async非同步執行有返回值的任務,並獲取任務執行結果。
定義AsyncTask類,創建兩個帶返回值的非同步方法,返回值類型為Future,task1執行時間5s,task2執行時間10s,在兩個方法上添加@Async
@Service @Slf4j public class AsyncTask { @Async public Future<String> task1() throws InterruptedException { log.info("task1 start"); Thread.sleep(5000L); log.info("task1 end"); return new AsyncResult<>("task1 result"); } @Async public Future<Integer> task2() throws InterruptedException { Integer abc = 1; log.info("task2 start"); Thread.sleep(10000L); log.info("task2 end"); return new AsyncResult<>(abc); } }
定義測試類,分別調用task1、task2,並等待task1和task2執行完畢
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; @Test public void taskTest() throws InterruptedException { log.info("taskTest start"); Future<String> task1Future = asyncTask.task1(); Future<Integer> task2Future = asyncTask.task2(); // do something for (int i = 0; i < 1000; i++) { } while (!task1Future.isDone() || !task2Future.isDone()) { } log.info("taskTest end"); } }
運行結果:
INFO [17:54:24.554][main][com.breezek.demo.common.AsyncTaskTest][28]:taskTest start INFO [17:54:24.566][task-1][com.breezek.demo.common.AsyncTask][27]:task1 start INFO [17:54:24.566][task-2][com.breezek.demo.common.AsyncTask][36]:task2 start INFO [17:54:29.569][task-1][com.breezek.demo.common.AsyncTask][29]:task1 end INFO [17:54:34.570][task-2][com.breezek.demo.common.AsyncTask][38]:task2 end INFO [17:54:34.570][main][com.breezek.demo.common.AsyncTaskTest][34]:taskTest end
可以看出來,main執行緒等待兩個子執行緒執行完畢後再繼續向下運行
使用@Async註解時,需要注意以下幾點,否則非同步調用不會生效:
- 非同步方法不能定義為static類型
- 調用方法和非同步方法不能定義在同一個類中
AsyncConfigurer配置
下面的程式碼中是如何配置非同步調用使用的執行緒池、void返回值異常捕獲處理
AsyncConfigurer介面是Spring提供的,我們定義JavaConfig時實現它:
@Configuration @EnableAsync @Slf4j public class AsyncConfig implements AsyncConfigurer { /** * 配置執行緒池,減少在調用每個非同步方法時創建和銷毀執行緒所需的時間 */ @Override public Executor getAsyncExecutor() { // 初始化Spring框架提供的執行緒池 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心執行緒數 executor.setCorePoolSize(10); // 最大執行緒數 executor.setMaxPoolSize(20); // 任務等待隊列大小 executor.setQueueCapacity(10); // 任務拒絕策略,如果執行緒池拒絕接受任務,使用調用執行緒執行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 定義執行緒名稱前綴 executor.setThreadNamePrefix("async-executor-"); // 調用執行緒池初始化方法,如果在getAsyncExecutor()加上了@Bean註解,這個方法可以不調用,因為ThreadPoolTaskExecutor實現了InitializingBean介面,Spring在初始化Bean時會調用InitializingBean.afterPropertiesSet() executor.initialize(); return executor; } /** * void返回值非同步方法異常捕獲處理 */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncExceptionHandler(); } /** * 異常捕獲處理類 */ public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { log.error(String.format("Async method: %s has uncaught exception, params: %s.", method, JSON.toJSONString(params)), ex); } } }