1. 介绍

@Async 注解在 service 内的方法上(@EnableAsync开启异步),可以实现在 controller 的异步调用,调用的被 @Async 注解的方法会在一个单独线程内运行,适合即使返回,异步解耦,service 慢慢去处理

@Async 注解的方法只能 返回 void 或者 future 类型的返回值,其他值会使 注解无效,因为不能异步执行

@Async 的方法在独立线程调用,不能被 @ControllerAdvice 全局异常处理器捕获,所以需要自己设置异常处理

  1. import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.scheduling.annotation.AsyncConfigurer;
  4. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  5. import java.util.concurrent.Executor;
  6. /**
  7. * @description:
  8. * @author: qinyu
  9. * @Date: 2019/8/22 21:09
  10. */
  11. @Configuration
  12. public class ThreadAsyncConfiguration implements AsyncConfigurer {
  13. @Override
  14. public Executor getAsyncExecutor() {
  15. ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
  16. //设置核心线程数
  17. threadPool.setCorePoolSize(10);
  18. //设置最大线程数
  19. threadPool.setMaxPoolSize(100);
  20. //线程池所使用的缓冲队列
  21. threadPool.setQueueCapacity(10);
  22. //等待任务在关机时完成--表明等待所有线程执行完
  23. threadPool.setWaitForTasksToCompleteOnShutdown(true);
  24. // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
  25. threadPool.setAwaitTerminationSeconds(60);
  26. // 线程名称前缀s
  27. threadPool.setThreadNamePrefix("MyAsync-");
  28. // 初始化线程
  29. threadPool.initialize();
  30. return threadPool;
  31. }
  32. @Override
  33. public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
  34. return new SpringAsyncExceptionHandler();
  35. }
  36. }

SpringAsyncExceptionHandler 类

  1. @Slf4j
  2. public class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
  3. @Override
  4. public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
  5. log.error("Exception occurs in async method", throwable.getMessage());
  6. }
  7. }

2. 使用实例一

springboot 使用异步注解 @Async

  1. @Component
  2. public class AsyncExceptionDemo {
  3. private static final Logger log = LoggerFactory.getLogger(AsyncExceptionDemo.class);
  4. /**
  5. * 最简单的异步调用,返回值为void
  6. */
  7. @Async
  8. public void asyncInvokeSimplest() {
  9. log.info("asyncSimplest");
  10. }
  11. /**
  12. * 带参数的异步调用 异步方法可以传入参数
  13. * 对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉
  14. * @param s
  15. */
  16. @Async
  17. public void asyncInvokeWithException(String s) {
  18. log.info("asyncInvokeWithParameter, parementer={}", s);
  19. throw new IllegalArgumentException(s);
  20. }
  21. /**
  22. * 异常调用返回Future
  23. * 对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理
  24. * 或者在调用方在调用Futrue.get时捕获异常进行处理
  25. *
  26. * @param i
  27. * @return
  28. */
  29. @Async
  30. public Future<String> asyncInvokeReturnFuture(int i) {
  31. log.info("asyncInvokeReturnFuture, parementer={}", i);
  32. Future<String> future;
  33. try {
  34. Thread.sleep(1000 * 1);
  35. future = new AsyncResult<String>("success:" + i);
  36. throw new IllegalArgumentException("a");
  37. } catch (InterruptedException e) {
  38. future = new AsyncResult<String>("error");
  39. } catch(IllegalArgumentException e){
  40. future = new AsyncResult<String>("error-IllegalArgumentException");
  41. }
  42. return future;
  43. }
  44. }

启动异步调用

  1. @SpringBootApplication
  2. @EnableAsync // 启动异步调用
  3. public class AsyncApplicationWithAsyncConfigurer {
  4. private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithAsyncConfigurer.class);
  5. public static void main(String[] args) {
  6. log.info("Start AsyncApplication.. ");
  7. SpringApplication.run(AsyncApplicationWithAsyncConfigurer.class, args);
  8. }
  9. }

3. 结合 CountDownLatch 异步获取结果

调用

  1. @Override
  2. public Result queryQuestionUseCountData(List<String> queIdList) throws ServiceException {
  3. try {
  4. if (ListUtils.isEmpty(queIdList)) {
  5. throw new Exception("试题id不能为空");
  6. }
  7. CountDownLatch countDownLatch = new CountDownLatch(3);
  8. //查询相似题数量
  9. Future<Map> relativeQuestionCountMapFuture = questionAsyncService.queryRelativeQuestionCount(queIdList, countDownLatch);
  10. //查询试题使用数据
  11. Future<List> questionStateCountListFuture = questionAsyncService.queryQuestionStateCountList(queIdList, countDownLatch);
  12. // 查询网校使用数量
  13. Future<List> onlineSchoolPaperNumberFuture = questionAsyncService.queryOnlineSchoolUseNumList(queIdList, countDownLatch);
  14. try {
  15. countDownLatch.await(10, TimeUnit.SECONDS);
  16. } catch (InterruptedException e) {
  17. throw new ServiceException("子线程等待异常");
  18. }
  19. // 封装试题使用数据返回结果
  20. Map<String, QueUseCountVo> returnMap = packageQuestionUseCountDataResult(queIdList, relativeQuestionCountMapFuture.get(), questionStateCountListFuture.get(), onlineSchoolPaperNumberFuture.get());
  21. // 根据配置字参数对使用/作答/正确率三个参数的显示进行过滤
  22. filterQuestionCountData(returnMap);
  23. return Result.buildSuccessResult(returnMap);
  24. } catch (Exception e) {
  25. log.error("查询试题使用数据失败.", e);
  26. throw new ServiceException(e);
  27. }
  28. }

结合 @Async 异步方法

  1. /**
  2. * @description: @Async 标注的方法和调用者不能在同一个类中,会产生代理绕过问题,因此创建此类放置异步方法
  3. * @author: zcq
  4. * @date: 2020/8/4 4:43 下午
  5. */
  6. @Service
  7. @Slf4j
  8. public class QuestionServiceAsyncImpl implements IQuestionAsyncService {
  9. @Autowired
  10. private DataCenterRestClient dataCenterRestClient;
  11. @Autowired
  12. private EsRestClient esRestClient;
  13. @Autowired
  14. private EpAnalyRestClient epAnalyRestClient;
  15. /**
  16. * @description: 查询相似题数量
  17. * @author: zcq
  18. * @date: 2020/8/3 11:16 上午
  19. */
  20. @Override
  21. @Async
  22. public Future<Map> queryRelativeQuestionCount(List<String> queIdList, CountDownLatch countDownLatch) throws Exception {
  23. Map<String, Object> relativeQuestionCountMap = new HashMap<>();
  24. try {
  25. Result relativeQuestionCountResult = dataCenterRestClient.queryRelativeQuestionCount(queIdList);
  26. if (relativeQuestionCountResult.getSuccess()) {
  27. relativeQuestionCountMap = (Map<String, Object>) relativeQuestionCountResult.getData();
  28. }
  29. return new AsyncResult(relativeQuestionCountMap);
  30. } catch (RestClientException e) {
  31. log.error("通过DataCenterRestClient 查询相似题数量失败.", e);
  32. throw new Exception(e);
  33. } finally {
  34. countDownLatch.countDown();
  35. }
  36. }
  37. /**
  38. * @description: 查询试题使用数据
  39. * @author: zcq
  40. * @date: 2020/8/3 11:16 上午
  41. */
  42. @Override
  43. @Async
  44. public Future<List> queryQuestionStateCountList(List<String> queIdList, CountDownLatch countDownLatch) throws Exception {
  45. List<QueUseCountVo> questionStateCountList = new ArrayList<>();
  46. try {
  47. Result questionStateCountResult = esRestClient.queryQuestionState(queIdList);
  48. if (questionStateCountResult.getSuccess() && questionStateCountResult.getData() != null) {
  49. questionStateCountList = JSON.parseArray(JSON.toJSONString(questionStateCountResult.getData()), QueUseCountVo.class);
  50. }
  51. return new AsyncResult(questionStateCountList);
  52. } catch (RestClientException e) {
  53. log.error("通过EsRestClient 获取试题统计信息失败.", e);
  54. throw new Exception(e);
  55. } finally {
  56. countDownLatch.countDown();
  57. }
  58. }
  59. /**
  60. * @description: 查询试题网校的使用数据
  61. * @author: zcq
  62. * @date: 2020/8/3 11:16 上午
  63. */
  64. @Override
  65. @Async
  66. public Future<List> queryOnlineSchoolUseNumList(List<String> queIdList, CountDownLatch countDownLatch) throws Exception {
  67. List<QueUseCountVo> onlineSchoolUseNumList = new ArrayList<>();
  68. try {
  69. Result questionWxUseCountResult = epAnalyRestClient.queryOnlineSchoolUseNumList(queIdList);
  70. if (questionWxUseCountResult.getSuccess() && questionWxUseCountResult.getData() != null) {
  71. onlineSchoolUseNumList = JSON.parseArray(JSON.toJSONString(questionWxUseCountResult.getData()), QueUseCountVo.class);
  72. }
  73. return new AsyncResult(onlineSchoolUseNumList);
  74. } catch (RestClientException e) {
  75. log.error("调用接口查询网校试题使用数据失败.", e);
  76. throw new Exception(e);
  77. } finally {
  78. countDownLatch.countDown();
  79. }
  80. }
  81. }

注意:

@Async 异步方法不要和同步调用方法写在同一个类中,即异步方法和调用者不要再同一个类中,会产生代理绕过问题。