不僅會用@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思維導圖

做個小調查:

留言區留言:你們公司是用什麼方式實現非同步處理業務的?

Tags: