@

“异步调用”对应的是“同步调用”,

在实际开发中,有时候为了及时处理请求和进行响应,我们可能使用异步调用,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。异步调用的实现有很多,例如多线程、定时任务、消息队列等。

这里学习使用@Async注解来实现异步调用。

1、@EnableAsync

首先,我们需要在启动类上添加 @EnableAsync 注解来声明开启异步方法。

  1. @SpringBootApplication
  2. @EnableAsync
  3. public class SpringbootAsyncApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(SpringbootAsyncApplication.class, args);
  6. }
  7. }

2、@Async

需要注意的,@Async在使用上有一些限制:

  • 它只能应用于public修饰的方法
  • 自调用–从同一个类中调用async方法,将不起作用

原因很简单:

  • 只有公共方法,才可以被代理。
  • 自调用不起作用,因为它越过了代理直接调用了方法。

2.1、无返回值的异步方法

这是一个异步运行的无返回值方法:

  1. @Async
  2. public void asyncMethodWithVoidReturnType() {
  3. System.out.println("异步无返回值方法 "
  4. + Thread.currentThread().getName());
  5. }

实例:

  • AsyncTask:异步式任务类,定义了三个异步式方法。
  1. /**
  2. * @Author 三分恶
  3. * @Date 2020/7/15
  4. * @Description 异步式任务
  5. */
  6. @Component
  7. public class AsyncTask {
  8. Logger log= LoggerFactory.getLogger(AsyncTask.class);
  9. private Random random = new Random();
  10. /**
  11. * 定义三个异步式方法
  12. * @throws InterruptedException
  13. */
  14. @Async
  15. public void taskOne() throws InterruptedException {
  16. long start = System.currentTimeMillis();
  17. //随机休眠若干毫秒
  18. Thread.sleep(random.nextInt(10000));
  19. long end = System.currentTimeMillis();
  20. log.info("任务一执行完成耗时{}秒", (end - start)/1000f);
  21. }
  22. @Async
  23. public void taskTwo() throws InterruptedException {
  24. long start = System.currentTimeMillis();
  25. Thread.sleep(random.nextInt(10000));
  26. long end = System.currentTimeMillis();
  27. log.info("任务二执行完成耗时{}秒", (end - start)/1000f);
  28. }
  29. @Async
  30. public void taskThree() throws InterruptedException {
  31. long start = System.currentTimeMillis();
  32. Thread.sleep(random.nextInt(10000));
  33. long end = System.currentTimeMillis();
  34. log.info("任务三执行完成耗时{}秒", (end - start)/1000f);
  35. }
  36. }
  • 在测试类中调用三个异步式方法:
  1. /**
  2. * @Author 三分恶
  3. * @Date 2020/7/15
  4. * @Description
  5. */
  6. @SpringBootTest
  7. @RunWith(SpringRunner.class)
  8. public class AsyncTaskTest {
  9. @Autowired
  10. private AsyncTask asyncTask;
  11. Logger log= LoggerFactory.getLogger(AsyncTaskTest.class);
  12. @Test
  13. public void doAsyncTasks(){
  14. try {
  15. long start = System.currentTimeMillis();
  16. //调用三个异步式方法
  17. asyncTask.taskOne();
  18. asyncTask.taskTwo();
  19. asyncTask.taskThree();
  20. Thread.sleep(5000);
  21. long end = System.currentTimeMillis();
  22. log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }

运行结果:可以看到三个方法没有顺序执行,这个复执行单元测试,您可能会遇到各种不同的结果,比如:

  • 没有任何任务相关的输出

    • 有部分任务相关的输出

      • 乱序的任务相关的输出

原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。

2.1、有返回值的异步方法

@Async也可以应用有返回值的方法–通过在Future中包装实际的返回值:

  1. /**
  2. * 有返回值的异步方法
  3. * @return
  4. */
  5. @Async
  6. public Future<String> asyncMethodWithReturnType() {
  7. System.out.println("执行有返回值的异步方法 "
  8. + Thread.currentThread().getName());
  9. try {
  10. Thread.sleep(5000);
  11. return new AsyncResult<String>("hello world !!!!");
  12. } catch (InterruptedException e) {
  13. //
  14. }
  15. return null;
  16. }

Spring还提供了一个实现Future的AsyncResult类。这个类可用于跟踪异步方法执行的结果。

实例:

  • 我们将2.1的实例改造成有返回值的异步方法:
  1. @Async
  2. public Future<String> taskOne() throws InterruptedException {
  3. long start = System.currentTimeMillis();
  4. //随机休眠若干毫秒
  5. Thread.sleep(random.nextInt(10000));
  6. long end = System.currentTimeMillis();
  7. log.info("任务一执行完成耗时{}秒", (end - start)/1000f);
  8. return new AsyncResult<>("任务一完事了");
  9. }

taskTwo、taskThree方法做同样的改造。

  • 测试有返回值的异步方法:
  1. @Test
  2. public void doFutureTask(){
  3. try {
  4. long start=System.currentTimeMillis();
  5. Future<String> future1=asyncTask.taskOne();
  6. Future <String> future2 = asyncTask.taskTwo();
  7. Future <String> future3 = asyncTask.taskThree();
  8. //三个任务执行完再执行主程序
  9. do {
  10. Thread.sleep(100);
  11. } while (future1.isDone() && future2.isDone() && future3.isDone());
  12. log.info("获取异步方法的返回值:{}", future1.get());
  13. Thread.sleep(5000);
  14. long end = System.currentTimeMillis();
  15. log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. } catch (ExecutionException e) {
  19. e.printStackTrace();
  20. }
  21. }

运行结果:可以看到三个任务完成后才执行主程序,还输出了异步方法的返回值。

3、 Executor

默认情况下,Spring使用SimpleAsyncTaskExecutor异步运行这些方法。

可以在两个级别上重写默认线程池——应用程序级别或方法级别。

3.1、方法级别重写Executor

所需的执行程序需要在配置类中声明 Executor:

  1. /**
  2. * @Author 三分恶
  3. * @Date 2020/7/15
  4. * @Description 方法级别重写线程池
  5. */
  6. @Configuration
  7. @EnableAsync
  8. public class SpringAsyncConfig {
  9. @Bean(name = "threadPoolTaskExecutor")
  10. public Executor threadPoolTaskExecutor() {
  11. return new ThreadPoolTaskExecutor();
  12. }
  13. }

然后,在@Async中的属性提供Executor名称:

  1. @Async("threadPoolTaskExecutor")
  2. public void asyncMethodWithConfiguredExecutor() {
  3. System.out.println("Execute method with configured executor - "
  4. + Thread.currentThread().getName());
  5. }

3.2、应用级别重写Executor

配置类应实现AsyncConfigurer接口,重写getAsyncExecutor()方法。

在这里,我们将返回整个应用程序的Executor,这样一来,它就成为运行以@Async注释的方法的默认Executor:

  1. /**
  2. * @Author 三分恶
  3. * @Date 2020/7/15
  4. * @Description 应用级别重写 Excutor
  5. */
  6. @Configuration
  7. @EnableAsync
  8. public class SpringApplicationAsyncConfig implements AsyncConfigurer {
  9. @Override
  10. public Executor getAsyncExecutor() {
  11. return new ThreadPoolTaskExecutor();
  12. }
  13. }

3.3、自定义线程池配置

在上面,自定义线程池只是简单地返回了一个线程池:

  1. return new ThreadPoolTaskExecutor();

实际上,还可以对线程池做一些配置:

  1. /**
  2. * @Author 三分恶
  3. * @Date 2020/7/15
  4. * @Description
  5. */
  6. @Configuration
  7. @EnableAsync
  8. public class SpringPropertiesAsyncConfig implements AsyncConfigurer {
  9. /**
  10. * 对线程池进行配置
  11. * @return
  12. */
  13. @Override
  14. public Executor getAsyncExecutor() {
  15. ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
  16. taskExecutor.setCorePoolSize(20);
  17. taskExecutor.setMaxPoolSize(200);
  18. taskExecutor.setQueueCapacity(25);
  19. taskExecutor.setKeepAliveSeconds(200);
  20. taskExecutor.setThreadNamePrefix("oKong-");
  21. // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
  22. taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  23. taskExecutor.initialize();
  24. return taskExecutor;
  25. }
  26. }

ThreadPoolTaskExecutor配置参数的简单说明:

  • corePoolSize:线程池维护线程的最少数量

  • keepAliveSeconds:允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁

  • maxPoolSize:线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程

  • queueCapacity:缓存队列

  • rejectedExecutionHandler:线程池对拒绝任务(无线程可用)的处理策略。这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。还有一个是AbortPolicy策略:处理程序遭到拒绝将抛出运行时RejectedExecutionException。

4、异常处理

当方法返回类型为Future时,异常处理很容易– Future.get()方法将抛出异常。

但是如果是无返回值的异步方法,异常不会传播到调用线程。因此,我们需要添加额外的配置来处理异常。

我们将通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序。

当存在任何未捕获的异步异常时,将调用handleUncaughtException()方法:

  1. /**
  2. * @Author 三分恶
  3. * @Date 2020/7/15
  4. * @Description
  5. */
  6. public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
  7. @Override
  8. public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
  9. System.out.println("Exception message - " + throwable.getMessage());
  10. System.out.println("Method name - " + method.getName());
  11. for (Object param : objects) {
  12. System.out.println("Parameter value - " + param);
  13. }
  14. }
  15. }

上面,我们使用配置类实现了AsyncConfigurer接口。

作为其中的一部分,我们还需要重写getAsyncUncaughtExceptionHandler()方法以返回我们的自定义异步异常处理:

  1. /**
  2. * 返回自定义异常处理
  3. * @return
  4. */
  5. @Override
  6. public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
  7. return new CustomAsyncExceptionHandler();
  8. }

5、总结

这里异步请求的使用及相关配置,如超时,异常等处理。在剥离一些和业务无关的操作时,就可以考虑使用异步调用进行其他无关业务操作,以此提供业务的处理效率。或者一些业务场景下可拆分出多个方法进行同步执行又互不影响时,也可以考虑使用异步调用方式提供执行效率。


本文为学习笔记类博客,学习资料来源见参考!

参考:

【1】:《深入浅出SpringBoot 2.x》

【2】:Spring Boot中使用@Async实现异步调用

【3】:SpringBoot 中异步执行任务的 2 种方式

【4】:How To Do @Async in Spring

【5】:SpringBoot系列:Spring Boot异步调用@Async

【6】:SpringBoot | 第二十一章:异步开发之异步调用

【7】:实战Spring Boot 2.0系列(三) - 使用@Async进行异步调用详解

SpringBoot学习笔记(十七:异步调用)的更多相关文章

  1. SpringBoot学习笔记(11):使用WebSocket构建交互式Web应用程序

    SpringBoot学习笔记(11):使用WebSocket构建交互式Web应用程序 快速开始 本指南将引导您完成创建“hello world”应用程序的过程,该应用程序在浏览器和服务器之间来回发送消 ...

  2. Springboot学习笔记(六)-配置化注入

    前言 前面写过一个Springboot学习笔记(一)-线程池的简化及使用,发现有个缺陷,打个比方,我这个线程池写在一个公用服务中,各项参数都定死了,现在有两个服务要调用它,一个服务的线程数通常很多,而 ...

  3. SpringBoot学习笔记(2):引入Spring Security

    SpringBoot学习笔记(2):用Spring Security来保护你的应用 快速开始 本指南将引导您完成使用受Spring Security保护的资源创建简单Web应用程序的过程. 参考资料: ...

  4. ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则

    ASP.NET MVC 学习笔记-7.自定义配置信息   ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...

  5. SpringBoot学习笔记:Swagger实现文档管理

    SpringBoot学习笔记:Swagger实现文档管理 Swagger Swagger是一个规范且完整的框架,用于生成.描述.调用和可视化RESTful风格的Web服务.Swagger的目标是对RE ...

  6. python3.4学习笔记(十七) 网络爬虫使用Beautifulsoup4抓取内容

    python3.4学习笔记(十七) 网络爬虫使用Beautifulsoup4抓取内容 Beautiful Soup 是用Python写的一个HTML/XML的解析器,它可以很好的处理不规范标记并生成剖 ...

  7. Web Service学习笔记:动态调用WebService

    原文:Web Service学习笔记:动态调用WebService 多数时候我们通过 "添加 Web 引用..." 创建客户端代理类的方式调用WebService,但在某些情况下我 ...

  8. SpringBoot学习笔记

    SpringBoot个人感觉比SpringMVC还要好用的一个框架,很多注解配置可以非常灵活的在代码中运用起来: springBoot学习笔记: .一.aop: 新建一个类HttpAspect,类上添 ...

  9. SpringBoot学习笔记(14):使用SpringBootAdmin管理监控你的应用

    SpringBoot学习笔记(14):使用SpringBootAdmin管理监控你的应用 Spring Boot Admin是一个管理和监控Spring Boot应用程序的应用程序.本文参考文档: 官 ...

  10. SpringBoot学习笔记(3):静态资源处理

    SpringBoot学习笔记(3):静态资源处理 在web开发中,静态资源的访问是必不可少的,如:Html.图片.js.css 等资源的访问. Spring Boot 对静态资源访问提供了很好的支持, ...

随机推荐

  1. 税务ukey如何批量开票

    最近税局开始大力推税务ukey版本,不过目前接口还未开放,就连航信,百旺否还没有对应接口,所以自己研究了下,在之前税控基础上,谁知道搞定了,通过安装插件可以批量开票,包括纸质,电子发票ofd格式. 联 ...

  2. cb51a_c++_STL_算法_根据第n个元素排序nth_element

    cb51a_c++_STL_算法_根据第n个元素排序nth_elementnth_element(b,n,e),比如最大的5个数排序,或者最小的几个数nth_element(b,n,e,p)对比:pa ...

  3. 深入了解C#(TPL)之Parallel.ForEach异步

    前言 最近在做项目过程中使用到了如题并行方法,当时还是有点犹豫不决,因为平常使用不多, 于是借助周末时间稍微深入了下,发现我用错了,故此做一详细记录,希望对也不是很了解的童鞋在看到本文此文后不要再犯和 ...

  4. Java并发编程-深入Java同步器AQS原理与应用-线程锁必备知识点

    并发编程中我们常会看到AQS这个词,很多朋友都不知道是什么东东,博主经过翻阅一些资料终于了解了,直接进入主题. 简单介绍 AQS是AbstractQueuedSynchronizer类的缩写,这个不用 ...

  5. skywalking的核心概念

    在 SkyWalking 中,TraceSegment 是一个介于 Trace 与 Span 之间的概念,它是一条 Trace 的一段,可以包含多个 Span.在微服务架构中,一个请求基本都会涉及跨进 ...

  6. java SSM框架单元测试最佳实战代码

    具体的代码参考链接:https://pan.baidu.com/s/1e9UTyidi4OMBwYydhwH-0g 密码:rmvs 本教程采用的是对单元测试的dao层.service层.control ...

  7. vue cli3项目中使用qrcodejs2生成二维码

    组件的形式创建 1.下载依赖 npm install qrcodejs2 2.创建一个.vue的组件放置代码(我创建的是qrcodejs2.vue) //template中的代码 <templa ...

  8. Python 简明教程 --- 18,Python 面向对象

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 代码能借用就借用. -- Tom Duff 目录 编程可分为面向过程编程和面向对象编程,它们是两种不 ...

  9. Codeforces Round #651 (Div. 2)

    感觉自己无可救药了. A题:找到小于等于n的两个不同的数的gcd最大是多少,显然是floort(n/2).设这两数是a * gcd, b * gcd然后gcd(a,b) = 1,那么gcd要尽量大,不 ...

  10. 使用nginx配置域名及禁止直接通过IP访问网站

    前段时间刚搭建好个人网站,一直没有关注一个问题,那就是IP地址也可以访问我的网站,今天就专门研究了一下nginx配置问题,争取把这个问题研究透彻. 1. nginx配置域名及禁止直接通过IP访问 先来 ...