Spring中的使用@Async异步调用方法

目录
  • 使用@Async异步调用方法
    • Async简介
      • 给Spring的TaskExecutor去完成
    • 本次记录Async使用场景
  • 异步请求与异步调用的区别
    • 异步请求的实现
    • SpringBoot中异步调用的使用

使用@Async异步调用方法

Async简介

异步方法调用使用场景:处理日志、发送邮件、短信......

spring中提供了@Async来实现异步方法。

@Async修饰类,则该类所有方法都是异步的,@Async修饰方法,则该方法是异步的。

被修饰的方法在被调用时,会在一个新的线程中执行。

Spring中通过在方法上设置@Async注解,可使得方法被异步调用。也就是该方法会在调用时立即返回,而这个方法的实际执行交

给Spring的TaskExecutor去完成

1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

2. 如果此时线程池中的数量等于corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。

4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

5. 当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

本次记录Async使用场景

需要调用其他服务,并且主线程需要继续完成当前线程任务

第一步:需要去做事的类

@Component
@EnableScheduling
public class VideoStatusUpdateServiceImpl implements VideoStatusUpdateService {
    @Resource
    private VaCaseVideoExtMapper vaCaseVideoExtMapper;
    //每隔五秒
    @Scheduled(cron = "*/5 * * * * ? ")
    @Override
    public void videoStatusUpdate() throws IOException {
        //得到一个集合
        List<VaCaseVideo> list = vaCaseVideoExtMapper.selectAllVideoes();
        //遍历集合去创建异步线程,去做一些其他事情
        for (VaCaseVideo vo : list) {
            dealTask(vo);
        }
    }
    @Async("asyncServiceExecutor")
    public void dealTask(VaCaseVideo vo) throws IOException {
       System.out.print("这里在做某件事情")
    }
}

第二步:启动类上加上注解@EnableAsync,开启异步

@SpringBootApplication
@EnableAsync
@EnableCaching
public class StartApp {
 public static void main(String[] args) {
  SpringApplication.run(StartApp.class, args);
 }
}

第三步:配置Executor(此步骤可有可无,若不配值则会使用默认值),配置自定义Executor

@Configuration
public class ExecutorConfig {
    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
    @Bean
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(5);
        //配置最大线程数
        executor.setMaxPoolSize(60);
        executor.setKeepAliveSeconds(180);
        //配置队列大小
        executor.setQueueCapacity(60);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}

第四步:启动项目,会每隔五秒打印需要做的事情

异步请求与异步调用的区别

两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。

异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

异步请求的实现

方式一:Servlet方式实现异步请求

  @RequestMapping(value = "/email/servletReq", method = GET)
  public void servletReq (HttpServletRequest request, HttpServletResponse response) {
      AsyncContext asyncContext = request.startAsync();
      //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
      asyncContext.addListener(new AsyncListener() {
          @Override
          public void onTimeout(AsyncEvent event) throws IOException {
              System.out.println("超时了...");
              //做一些超时后的相关操作...
          }
          @Override
          public void onStartAsync(AsyncEvent event) throws IOException {
              System.out.println("线程开始");
          }
          @Override
          public void onError(AsyncEvent event) throws IOException {
              System.out.println("发生错误:"+event.getThrowable());
          }
          @Override
          public void onComplete(AsyncEvent event) throws IOException {
              System.out.println("执行完成");
              //这里可以做一些清理资源的操作...
          }
      });
      //设置超时时间
      asyncContext.setTimeout(20000);
      asyncContext.start(new Runnable() {
          @Override
          public void run() {
              try {
                  Thread.sleep(10000);
                  System.out.println("内部线程:" + Thread.currentThread().getName());
                  asyncContext.getResponse().setCharacterEncoding("utf-8");
                  asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                  asyncContext.getResponse().getWriter().println("这是异步的请求返回");
              } catch (Exception e) {
                  System.out.println("异常:"+e);
              }
              //异步请求完成通知
              //此时整个请求才完成
              asyncContext.complete();
          }
      });
      //此时之类 request的线程连接已经释放了
      System.out.println("主线程:" + Thread.currentThread().getName());
  }

方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理

  @RequestMapping(value = "/email/callableReq", method = GET)
  @ResponseBody
  public Callable<String> callableReq () {
      System.out.println("外部线程:" + Thread.currentThread().getName());
      return new Callable<String>() {
          @Override
          public String call() throws Exception {
              Thread.sleep(10000);
              System.out.println("内部线程:" + Thread.currentThread().getName());
              return "callable!";
          }
      };
  }

  @Configuration
  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {

  @Resource
  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

  @Override
  public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
      //处理 callable超时
      configurer.setDefaultTimeout(60*1000);
      configurer.setTaskExecutor(myThreadPoolTaskExecutor);
      configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
  }

  @Bean
  public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
      return new TimeoutCallableProcessingInterceptor();
  }
}

方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理

   @RequestMapping(value = "/email/webAsyncReq", method = GET)
    @ResponseBody
    public WebAsyncTask<String> webAsyncReq () {
        System.out.println("外部线程:" + Thread.currentThread().getName());
        Callable<String> result = () -> {
            System.out.println("内部线程开始:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (Exception e) {
                // TODO: handle exception
            }
            logger.info("副线程返回");
            System.out.println("内部线程返回:" + Thread.currentThread().getName());
            return "success";
        };
        WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);
        wat.onTimeout(new Callable<String>() {

            @Override
            public String call() throws Exception {
                // TODO Auto-generated method stub
                return "超时";
            }
        });
        return wat;
    }

方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。

@RequestMapping(value = "/email/deferredResultReq", method = GET)
    @ResponseBody
    public DeferredResult<String> deferredResultReq () {
        System.out.println("外部线程:" + Thread.currentThread().getName());
        //设置超时时间
        DeferredResult<String> result = new DeferredResult<String>(60*1000L);
        //处理超时事件 采用委托机制
        result.onTimeout(new Runnable() {

            @Override
            public void run() {
                System.out.println("DeferredResult超时");
                result.setResult("超时了!");
            }
        });
        result.onCompletion(new Runnable() {

            @Override
            public void run() {
                //完成后
                System.out.println("调用完成");
            }
        });
        myThreadPoolTaskExecutor.execute(new Runnable() {

            @Override
            public void run() {
                //处理业务逻辑
                System.out.println("内部线程:" + Thread.currentThread().getName());
                //返回结果
                result.setResult("DeferredResult!!");
            }
        });
       return result;
    }

SpringBoot中异步调用的使用

1、介绍

异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

2、使用方式(基于spring下)

需要在启动类加入@EnableAsync使异步调用@Async注解生效

在需要异步执行的方法上加入此注解即可@Async("threadPool"),threadPool为自定义线程池

代码略。。。就俩标签,自己试一把就可以了

3、注意事项

在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。

调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。

其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。

4、什么情况下会导致@Async异步方法会失效?

  • a.调用同一个类下注有@Async异步方法:在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。
  • b.调用的是静态(static )方法
  • c.调用(private)私有化方法

5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)

将要异步执行的方法单独抽取成一个类,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。

其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以通过上下文获取自己的代理对象调用异步方法。

@Controller
@RequestMapping("/app")
public class EmailController {
    //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下
    @Autowired
    private ApplicationContext applicationContext;

    @RequestMapping(value = "/email/asyncCall", method = GET)
    @ResponseBody
    public Map<String, Object> asyncCall () {
        Map<String, Object> resMap = new HashMap<String, Object>();
        try{
            //这样调用同类下的异步方法是不起作用的
            //this.testAsyncTask();
            //通过上下文获取自己的代理对象调用异步方法
            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);
            emailController.testAsyncTask();
            resMap.put("code",200);
        }catch (Exception e) {
            resMap.put("code",400);
            logger.error("error!",e);
        }
        return resMap;
    }

    //注意一定是public,且是非static方法
    @Async
    public void testAsyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("异步任务执行完成!");
    }
}

6、开启cglib代理,手动获取Spring代理类,从而调用同类下的异步方法。

首先,在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解。

代码实现,如下:

@Service
@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
public class EmailService {

    @Autowired
    private ApplicationContext applicationContext;

    @Async
    public void testSyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("异步任务执行完成!");
    }
    public void asyncCallTwo() throws InterruptedException {
        //this.testSyncTask();
//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
//        emailService.testSyncTask();
        boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象;
        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //是否是CGLIB方式的代理对象;
        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //是否是JDK动态代理方式的代理对象;
        //以下才是重点!!!
        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
        EmailService proxy = (EmailService) AopContext.currentProxy();
        System.out.println(emailService == proxy ? true : false);
        proxy.testSyncTask();
        System.out.println("end!!!");
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Boot利用@Async异步调用:ThreadPoolTaskScheduler线程池的优雅关闭详解

    前言 之前分享了一篇关于Spring Boot中使用@Async来实现异步任务和线程池控制的文章:<Spring Boot使用@Async实现异步调用:自定义线程池>.由于最近身边也发现了不少异步任务没有正确处理而导致的不少问题,所以在本文就接前面内容,继续说说线程池的优雅关闭,主要针对ThreadPoolTaskScheduler线程池. 问题现象 在上篇文章的例子Chapter4-1-3中,我们定义了一个线程池,然后利用@Async注解写了3个任务,并指定了这些任务执行使用的线程池.在上文

  • 深入理解Spring注解@Async解决异步调用问题

    序言:Spring中@Async 根据Spring的文档说明,默认采用的是单线程的模式的.所以在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的. 那么当多个任务的执行势必会相互影响.例如,如果A任务执行时间比较长,那么B任务必须等到A任务执行完毕后才会启动执行.又如在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring3.x之后,已经内置了@Async来完美解决这个问题. 1. 何为异步调用? 在解释之前,我们先来看二

  • springboot实现异步调用@Async的示例

    在后端开发中经常遇到一些耗时或者第三方系统调用的情况,我们知道Java程序一般的执行流程是顺序执行(不考虑多线程并发的情况),但是顺序执行的效率肯定是无法达到我们的预期的,这时就期望可以并行执行,常规的做法是使用多线程或线程池,需要额外编写代码实现.在spring3.0后引入了@Async注解,使用该注解可以达到线程池的执行效果,而且在开发上非常简单. 一.概述 springboot是基于spring框架的,在springboot环境下演示@Async注解的使用方式.先看下该注解的定义, @Ta

  • Spring Boot异步调用@Async过程详解

    在实际开发中,有时候为了及时处理请求和进行响应,我们可能会多任务同时执行,或者先处理主任务,也就是异步调用,异步调用的实现有很多,例如多线程.定时任务.消息队列等, 我们来讲讲@Async异步方法调用. 一.@Async使用演示 @Async是Spring内置注解,用来处理异步任务,在SpringBoot中同样适用,且在SpringBoot项目中,除了boot本身的starter外,不需要额外引入依赖. 而要使用@Async,需要在启动类上加上@EnableAsync主动声明来开启异步方法. @

  • Spring Boot利用@Async异步调用:使用Future及定义超时详解

    前言 之前连续写了几篇关于使用@Async实现异步调用的内容,也得到不少童鞋的反馈,其中问题比较多的就是关于返回Future的使用方法以及对异步执行的超时控制,所以这篇就来一起讲讲这两个问题的处理. 如果您对于@Async注解的使用还不了解的话,可以看看之前的文章,具体如下: 使用@Async实现异步调用 使用@Async实现异步调用:自定义线程池 使用@Async实现异步调用:资源优雅关闭 定义异步任务 首先,我们先使用@Async注解来定义一个异步任务,这个方法返回Future类型,具体如下

  • Spring @async方法如何添加注解实现异步调用

    这篇文章主要介绍了Spring @async方法如何添加注解实现异步调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在我们使用spring框架的过程中,在很多时候我们会使用@async注解来异步执行某一些方法,提高系统的执行效率.今天我们来探讨下 spring 是如何完成这个功能的. spring 在扫描bean的时候会扫描方法上是否包含@async的注解,如果包含的,spring会为这个bean动态的生成一个子类,我们称之为代理类(?),

  • Spring中的使用@Async异步调用方法

    目录 使用@Async异步调用方法 Async简介 给Spring的TaskExecutor去完成 本次记录Async使用场景 异步请求与异步调用的区别 异步请求的实现 SpringBoot中异步调用的使用 使用@Async异步调用方法 Async简介 异步方法调用使用场景:处理日志.发送邮件.短信...... spring中提供了@Async来实现异步方法. @Async修饰类,则该类所有方法都是异步的,@Async修饰方法,则该方法是异步的. 被修饰的方法在被调用时,会在一个新的线程中执行.

  • Spring Boot集成教程之异步调用Async

    前言 本文主要给大家介绍了关于Spring Boot集成之异步调用Async的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 什么是异步调用? 异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行. 异步处理方式 调用之后,不返回任何数据. 调用之后,返回数据,通过Future来获取返回数据 如何实现异步调用? 多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实

  • 关于java中@Async异步调用详细解析附代码

    目录 前言 1. @Async讲解 2. 用法 2.1 同步调用 2.2 异步调用 3. 自定义线程池 前言 异步调用与同步调用 同步调用:顺序执行,通过调用返回结果再次执行下一个调用 异步调用:通过调用,无需等待返回结果,执行下一个调用 1. @Async讲解 其@Async的注解代码如下: @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public

  • SpringBoot异步调用方法实现场景代码实例

    一.背景 项目中肯定会遇到异步调用其他方法的场景,比如有个计算过程,需要计算很多个指标的值,但是每个指标计算的效率快慢不同,如果采用同步执行的方式,运行这一个过程的时间是计算所有指标的时间之和.比如: 方法A:计算指标x,指标y,指标z的值,其中计算指标x需要1s,计算指标y需要2s,指标z需要3s.最终执行完方法A就是5s. 现在用异步的方式优化一下 方法A异步调用方法B,方法C,方法D,方法B,方法C,方法D分别计算指标x,指标y,指标z的值,那么最终执行完方法A的时间则是3s. 还有一种用

  • SpringBoot异步调用方法并接收返回值

    项目中肯定会遇到异步调用其他方法的场景,比如有个计算过程,需要计算很多个指标的值,但是每个指标计算的效率快慢不同,如果采用同步执行的方式,运行这一个过程的时间是计算所有指标的时间之和.比如: 方法A:计算指标x,指标y,指标z的值,其中计算指标x需要1s,计算指标y需要2s,指标z需要3s.最终执行完方法A就是5s. 现在用异步的方式优化一下 方法A异步调用方法B,方法C,方法D,方法B,方法C,方法D分别计算指标x,指标y,指标z的值,那么最终执行完方法A的时间则是3s. 步骤1:配置线程池,

  • Spring中注解方式的异步请求

    一.Servlet3.0异步请求 @WebServlet(value = "/async", asyncSupported = true) public class HelloAsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //

  • php 异步调用方法实现示例

    php 异步调用方法 客户端与服务器端是通过HTTP协议进行连接通讯,客户端发起请求,服务器端接收到请求后执行处理,并返回处理结果. 有时服务器需要执行很耗时的操作,这个操作的结果并不需要返回给客户端.但因为php是同步执行的,所以客户端需要等待服务处理完才可以进行下一步. 因此对于耗时的操作适合异步执行,服务器接收到请求后,处理完客户端需要的数据就返回,再异步在服务器执行耗时的操作. 1.使用Ajax 与 img 标记 原理,服务器返回的html中插入Ajax 代码或 img 标记,img的s

  • python使用multiprocessing模块实现带回调函数的异步调用方法

    本文实例讲述了python使用multiprocessing模块实现带回调函数的异步调用方法.分享给大家供大家参考.具体分析如下: multipressing模块是python 2.6版本加入的,通过这个模块可以轻松实现异步调用 from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': pool = Pool(processes=1) # Start a worker processes. r

  • 浅谈Django中view对数据库的调用方法

    question: Django中对数据库的调用非常的隐蔽,在各种复杂的模块互相拼接继承中很难发现获取数据库内容的部分 来,开始试图理解一下下 首先,数据库中的表对应的是model中的每一个类,类中的变量对应表的属性,通常属性名就是变量名.有一个比较特殊的东西就是ForeignKey,它代表了与其他表的关联约束键,即SQL中的约束键,通常和其他表中的主键primary key相关联. 理解了model是我们定义的数据表,接下来的事情就会越发的简单,我们都知道网页中的data信息是通过Django

  • 浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数–封装独立的功能 2.调用函数–享受封装的成果 函数的作用:在开发时,使用函数可以提高编写的效率以及代码的重用'' 函数: 函数是带名字的代码块,用于完成具体的工作 需要在程序中多次执行同一项任务时,你无需反复编写完成该任务的代码,而只需调用该任务的函数,让python运行其中的代码,你将发现,通过使用函数,程序编写,阅读,测试和修复都将更容易 1.定义函数 def

随机推荐