不僅會用@Async,我把源碼也梳理了一遍(上)
- 2019 年 10 月 6 日
- 筆記

說起非同步化,很多人會想起非同步執行緒、消息隊列等,消息隊列不是文章的主題,今天我們來聊聊spring對非同步化的支援@EnableAsync&@Async。
我會分為以下幾個步驟去說明,首先說說用法,然後再從源碼層分析@Async的底層原理,同學們準備好了嗎?
@Async用法
想要開啟非同步化,我們就必須要用用到@EnableAsync註解,這又是我們之前給大家說個的@EnableXXX的模組,大家可以回顧一下:《導圖梳理springboot手動、自動裝配,讓springboot不再難懂》,配置類中開啟了@EnableAsync之後,@Async的註解就開始起作用了,我們先來做個簡單的測試:
配置類
- com.example.demo.config.AsyncConfig
@EnableAsync @Configuration public class AsyncConfig { }一個頁面控制器
- com.example.demo.controller.AsyncController
@Slf4j @RestController public class AsyncController { @Autowired UserService userService; @GetMapping("/tt") public Object tt() throws InterruptedException { String username = userService.findUserNameById(1L); log.info("AsyncController----------->" + Thread.currentThread().getName()); log.info("username---------------->" + username); return username; } }然後就是業務類
- com.example.demo.service.impl.UserServiceImpl
@Slf4j @Service public class UserServiceImpl implements UserService { @Async public String findUserNameById(Long id) { log.info("UserServiceImpl----------->" + Thread.currentThread().getName()); return "關注公眾號:java思維導圖"; } }好了,最簡單的用法已經完成了,我在UserServiceImpl#findUserNameById方法上添加了@Async註解,表示此方法是個非同步方法。我們先來看看執行結果,然後再來說說上面的程式有什麼問題。
控制台輸出:

可以看出,兩個方法執行的執行緒是不一樣的,明顯UserServiceImpl的findUserNameById方法是另起了一個執行緒task-1執行。
這裡我們注意到了一個問題,controller中我們獲取不到了非同步方法的返回值username了。所以想要獲取@Async註解之後的非同步方法就不能使用這種方式了,所以我們改下程式:
service中新添加個方法:
@Async public Future<String> findLastNameById(Long id) { log.info("UserServiceImpl----------->" + Thread.currentThread().getName()); return new AsyncResult<String>("關注公眾號:java思維導圖"); }然後controller改成這樣:
@GetMapping("/tt") public Object tt() throws InterruptedException, ExecutionException { String username = userService.findUserNameById(1L); log.info("AsyncController----------->" + Thread.currentThread().getName()); log.info("username---------------->" + username); Future<String> future = userService.findLastNameById(1L); String lastname = future.get(); log.info("lastname---------------->" + lastname + Thread.currentThread().getName()); return username; }測試結果如下:

可以看到task-1獲取不到結果,task-2獲取到結果了,但是這裡有個問題,為了獲取非同步的結果,執行緒http-nio-8080-exec-1一直在等待future.get()的返回結果,才繼續往下走,這就有點影響主執行緒的性能了。
那如果非同步方法出現了異常,我們怎麼去捕捉呢,一般全局異常處理我們都是處理controller中的異常,但這個非同步執行緒的異常能不能捕捉到都是個問題。其實,對於@Async的異常,是可以配置的。AsyncConfig中我們可以繼承一個AsyncConfigurer,然後重寫兩個方法:
- com.example.demo.config.AsyncConfig
@EnableAsync @Configuration public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { //執行緒池 ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(5); taskExecutor.setMaxPoolSize(10); taskExecutor.setQueueCapacity(25); taskExecutor.initialize(); return taskExecutor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new SimpleAsyncUncaughtExceptionHandler(); } }一個定義執行緒池,一個定義異常處理器,執行緒池我們就不說了,這個默認的SimpleAsyncUncaughtExceptionHandler沒啥東西:
public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class); @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { if (logger.isErrorEnabled()) { logger.error("Unexpected exception occurred invoking async method: " + method, ex); } } }所以如果我們想自己處理這個出現異常之後的邏輯,還得重寫AsyncUncaughtExceptionHandler 的handleUncaughtException介面,裡面有method、params等參數,可以進行重試、或者其他處理等。
非同步化原理
好吧,下一篇再寫了,今天先到這裡,下午還有點事~
待續~
結束語
堅持原創的第三篇(20190920),打卡打卡。希望你們會喜歡。
我是呂一明,歡迎關注我的公眾號:java思維導圖
做個小調查:
留言區留言:你們公司是用什麼方式實現非同步處理業務的?