在前边的文章中,和小伙伴一起认识了异步执行的好处,以及如何进行异步开发,对,就是使用@Async注解,在使用异步注解@Async的过程中也存在一些坑,不过通过正确的打开方式也可以很好的避免,今天想和大家分享下@Async的原理,开始前先温习下之前的文章哦,

springboot:异步调用@Async

springboot:使用异步注解@Async获取执行结果的坑

springboot:嵌套使用异步注解@Async还会异步执行吗

一、引言

在前边说到在使用@Async的时候,在一个类中两个@Async的方法嵌套使用会导致异步失败,下面把场景重现下,

AsyncContoller.java

  1. package com.example.myDemo.controller;
  2.  
  3. import com.example.myDemo.service.AsyncService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Controller;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. import java.util.concurrent.ExecutionException;
  9.  
  10. @Controller
  11. public class AsyncController {
  12. @Autowired
  13. private AsyncService asyncService;
  14. @GetMapping("/aysnc")
  15. @ResponseBody
  16. public String asyncMethod(){
  17. try {
  18. Long start=System.currentTimeMillis();
  19. //调用method3方法,该方法中嵌套了一个异步方法
  20. String str3=asyncService.method3().get();
  21. Long end=System.currentTimeMillis();
  22. System.out.println("执行时长:"+(end-start));
  23. } catch (ExecutionException | InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. return "hello @Async";
  27. }
  28. }

下面是method3方法

  1. package com.example.myDemo.service;
  2.  
  3. import org.springframework.scheduling.annotation.Async;
  4. import org.springframework.scheduling.annotation.AsyncResult;
  5. import org.springframework.stereotype.Service;
  6. import java.util.concurrent.Future;
  7.  
  8. @Service
  9. @Async
  10. public class AsyncService {
  11. /**
  12. * 第一个异步方法,睡眠10s返回字符串
  13. *
  14. * @return
  15. */
  16. public Future<String> method() {
  17. try {
  18. Thread.sleep(10 * 1000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. return new AsyncResult("I am method");
  23. }
  24.  
  25. /**
  26. * 第三个异步方法,在该异步方法中调用了另外一个异步方法
  27. * @return
  28. */
  29. public Future<String> method3(){
  30. try{
    //睡眠10s
  31. Thread.sleep(10*1000);
  32. System.out.println(this);
    //method方法也是睡眠10s
  33. this.method();
  34.  
  35. }catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. return new AsyncResult<>("two async method");
  39. }
  40. }

上面便是method3方法,以及嵌套在method3方法中的method方法,这两个方法体上均没有标注@Async,只是在这个类上使用了@Async注解,那么该类中的所有方法都是异步的。

执行结果如下,

  1. 2022-04-30 15:29:47.711 INFO 16836 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
  2. com.example.myDemo.service.AsyncService@7e316231
  3. 执行时长:20028

从上面可以看到整个方法的执行时长是20多秒,那么就说明这种同一个类中的嵌套调用,@Async是失效的

二、解决方式

1、把嵌套方法抽到另一个类中

这种方式就是把嵌套的异步方法method抽取到另外一个类中,下面我们来看下,

OtherService.java

  1. package com.example.myDemo.service;
  2.  
  3. import org.springframework.scheduling.annotation.Async;
  4. import org.springframework.scheduling.annotation.AsyncResult;
  5. import org.springframework.stereotype.Service;
  6. import java.util.concurrent.Future;
  7.  
  8. @Service
  9. @Async
  10. public class OtherAsyncService {
  11. public Future<String> method() {
  12. try {
  13. Thread.sleep(10 * 1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. return new AsyncResult("I am method");
  18. }
  19. }

那么AsyncService.java则变成下面的样子

  1. package com.example.myDemo.service;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.scheduling.annotation.Async;
  5. import org.springframework.scheduling.annotation.AsyncResult;
  6. import org.springframework.stereotype.Service;
  7.  
  8. import java.util.concurrent.Future;
  9.  
  10. @Service
  11. @Async
  12. public class AsyncService {
  13. //注入OtherService
  14. @Autowired
  15. private OtherAsyncService otherAsyncService;
  16.  
  17. /**
  18. * 第三个异步方法,在该异步方法中调用了另外一个异步方法
  19. * @return
  20. */
  21. public Future<String> method3(){
  22. try{
  23. Thread.sleep(10*1000);
  24. System.out.println(this);
  25. //调用OtherAsyncService的method方法
  26. otherAsyncService.method();
  27.  
  28. }catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. return new AsyncResult<>("two async method");
  32. }
  33. }

下面看执行的结果,

  1. 2022-04-30 15:44:18.914 INFO 16768 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
  2. com.example.myDemo.service.AsyncService@689927ef
  3. 执行时长:10016

执行时长10s多点,符合预期。

2、自己注入自己

这种方式很有意思,我斗胆给它取名为“自己注入自己”,在AsyncService类中注入一个AsyncService的实例,如下

  1. package com.example.myDemo.service;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.scheduling.annotation.Async;
  5. import org.springframework.scheduling.annotation.AsyncResult;
  6. import org.springframework.stereotype.Service;
  7.  
  8. import java.util.concurrent.Future;
  9.  
  10. @Service
  11. @Async
  12. public class AsyncService {
  13. //这里注入的是AsyncService的实例
    @Lazy
  14. @Autowired
  15. private AsyncService otherAsyncService;
  16. /**
  17. * 第一个异步方法,睡眠10s返回字符串
  18. *
  19. * @return
  20. */
  21. public Future<String> method() {
  22. try {
  23. Thread.sleep(10 * 1000);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. return new AsyncResult("I am method");
  28. }
  29. /**
  30. * 第三个异步方法,在该异步方法中调用了另外一个异步方法
  31. * @return
  32. */
  33. public Future<String> method3(){
  34. try{
  35. Thread.sleep(10*1000);
  36. System.out.println(this);
  37. otherAsyncService.method();
  38.  
  39. }catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. return new AsyncResult<>("two async method");
  43. }
  44. }

小伙伴们注意,我是在AsyncService类中又注入了一个AsyncService的实例,在method3方法中调用的是AsyncSerevice的方法method,要区别于下面的调用方式

  1. this.method();

下面看下执行结果,

  1. 2022-04-30 15:55:30.635 INFO 9788 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
  2. com.example.myDemo.service.AsyncService@2ac186f8
  3. 执行时长:10015

好了,我们看到执行时长为10s多点,也就是说异步是生效的,在这种方式中要注意注入的对象必须添加@Lazy注解,否则启动会报错哦。

三、原理揭秘

上面已经把嵌套使用的误区和解决方式已经总结完了,下面到了要揭开@Async面纱的时候了,最好的方式是debug,看下面@Async的debug的过程

可以看到在AsyncController中asyncService是一个代理对象,且使用的方式是cglib,那么也就是会把其中的方法进行代理,类似下面的代码

  1. before();
  2. method3();
  3. after();

也就是对method3进行了代理,这里的代理指的是把mthod3方法封装成一个task,交给线程池去执行,那么在method3中的this.method()这句调用,也就是普通调用了,是同步的,为什么这样说,因为这里的this代表的是AsyncService这个实例对象,

但是如果换成"自己注入自己的方式",例如下图,

可以看到还是一个AsyncService的cglib代理对象,所以完美解决了嵌套调用的问题。

四、总结

本文分析了@Async注解的实现原理及如何使用正确使用嵌套调用,

1、@Async注解底层使用的是代理,标记为@Async所在的类在实际调用时是一个代理类;

2、合理使用@Async方法的嵌套,可以把嵌套方法抽到另外一个类中;

3、如果在本类中使用嵌套方法,那么需要自己注入自己,切记加上@Lazy注解;

推荐阅读

springboot:异步调用@Async

springboot:使用异步注解@Async获取执行结果的坑

springboot:嵌套使用异步注解@Async还会异步执行吗

springboot:使用异步注解@Async的前世今生的更多相关文章

  1. springboot:使用异步注解@Async的那些坑

    springboot:使用异步注解@Async的那些坑 一.引言 在java后端开发中经常会碰到处理多个任务的情况,比如一个方法中要调用多个请求,然后把多个请求的结果合并后统一返回,一般情况下调用其他 ...

  2. springboot:嵌套使用异步注解@Async还会异步执行吗

    一.引言 在前边的文章<[springboot:使用异步注解@Async的那些坑>中介绍了使用@Async注解获取任务执行结果的错误用法,今天来分享下另外一种常见的错误. 二.代码演示 下 ...

  3. Spring中异步注解@Async的使用、原理及使用时可能导致的问题

    前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...

  4. springboot之异步调用@Async

    原文:http://www.cnblogs.com/xuwenjin/p/8858050.html 引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在处理与第三方系统交 ...

  5. springboot:异步调用@Async

    在后端开发中经常遇到一些耗时或者第三方系统调用的情况,我们知道Java程序一般的执行流程是顺序执行(不考虑多线程并发的情况),但是顺序执行的效率肯定是无法达到我们的预期的,这时就期望可以并行执行,常规 ...

  6. 关于Dubbo和Spring异步注解@Async的冲突

    项目中难免会有异步处理的需求,像异步记录日志啦,异步发送邮件啦,而Dubbo又是现在主流的分布式框架,所有异步+Dubbo的组合是再所难免的 但博主是实践中发现Dubbo的服务并不能很好的跟Sprin ...

  7. Java中异步注解@Async的陷阱

    或许,你在Java后端添加异步过程时会这样处理,然后摇摇大摆.灰溜溜地闪,而实际的运行结果却并不是我们期望的那样.那么,现在就将试验结果记录如下,以便少走弯路. (一)在Controller层的公开接 ...

  8. SpringBoot使用异步线程池实现生产环境批量数据推送

    前言 SpringBoot使用异步线程池: 1.编写线程池配置类,自定义一个线程池: 2.定义一个异步服务: 3.使用@Async注解指向定义的线程池: 这里以我工作中使用过的一个案例来做描述,我所在 ...

  9. @Async异步注解与SpringBoot结合使用

    当你在service层需要启动异步线程去执行某些分支任务,又不希望显式使用Thread等线程相关类,只想专注于实现业务逻辑代码开发,可以使用@Async异步注解. 1. 使用@Async 异步注解 C ...

随机推荐

  1. DWR是什么?有什么作用?

    DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站. 它可以允许在浏览器里的代码使用运行 ...

  2. Dubbo 可以对结果进行缓存吗?

    为了提高数据访问的速度.Dubbo 提供了声明式缓存,以减少用户加缓存的工作 量 <dubbo:reference cache="true" /> 其实比普通的配置文件 ...

  3. JVM的基础知识

    一.JVM的基础知识 1.JVM内存结构: 1.JVM堆内存结构: 2.JVM内存分配: 3.Java的堆机构和垃圾回收: 4.Jvm堆内存配置参数: 5.JVM新生代概念和配置: 6.JVM老生代概 ...

  4. 百度移动统计调用api教程,少进坑(82001错误)

    相信很多小伙伴使用了百度统计,来查看自己应用使用的情况,但是会发现百度移动统计在官网没有api调用取数据的接口, 现在我就以自己成功调用api并且成功拿到数据,将这个步骤给大家参考,(末尾有调用移动统 ...

  5. css技术之用最高和最宽的限制“max-height和max-width”做图片同比例缩放,达到图片不变形目的,做出批量打印图片功能,页面打印“window.print()”

    一.简介 他们是为流而生的,像width/height这种定死的砖头式布局,min-width/max-width就没有存在的意义 ,min-width/max-width一定是自适应布局或流体布局中 ...

  6. 攻防世界shrine

    shrine import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG' ...

  7. Cadence 错误合集

    1.原理图DRC出现如下错误"Duplicate Pin Name "GND" found on Packag" 解决方案:原因是元件引脚重复定义,可以进行重新 ...

  8. CSS:两端对齐原理(text-align:justify)

    我是一个小白我是一个小白我是一个小白喷我吧,哈哈 写样式的是时候经常会碰到字体两端对齐的效果,一般就网上找端css样式复制下就结束了,没有考虑过原理是啥贴下代码 <head> <me ...

  9. JavaScript 工作原理之三-内存管理及如何处理 4 类常见的内存泄漏问题(译)

    原文请查阅这里,本文有进行删减,文后增了些经验总结. 本系列持续更新中,Github 地址请查阅这里. 这是 JavaScript 工作原理的第三章. 我们将会讨论日常使用中另一个被开发者越来越忽略的 ...

  10. pip freeze > requirements.txt` 命令输出文件中出现文件路径而非版本号

    pip freeze > requirements.txt 命令输出文件中出现文件路径而非版本号 解决办法: pip list --format=freeze > requirements ...