0


Spring中@Async注解的使用

一、应用场景

1、同步调用
通常,在Java中的方法调用都是同步调用,比如在A方法中调用了B方法,则在A调用B方法之后,必须等待B方法执行并返回后,A方法才可以继续往下执行。

这样容易出现的一个问题就是如果B方法执行时间较长,则可能会导致调用A的请求响应迟缓 或者超时,严重影响用户体验。

为了解决这种问题,可以使用Spirng的注解@Async来用异步调用的方式处理。

2、异步调用
比如方法A调用方法B,如果B是一个异步方法,则A方法在调用B方法之后,不用等待B方法执行完成,而是直接往下继续执行别的代码。

这样,接口响应速度就会比较快。场景示例:商品库存更新接口,更新成功后,需要发送通知邮件,而接口的返回和邮件是否发送成功无关,那发送邮件这个步骤就可以写成1个异步方法进行调用。

二、含义

  1. 在方法上使用该@Async注解,申明该方法是一个异步任务;
  2. 在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;
  3. 使用此注解的方法的类对象,必须是spring管理下的bean对象;
  4. 要想使用异步任务,需要在主类上开启异步配置,即,配置上@EnableAsync注解;

三、使用步骤

1、启动类中增加@EnableAsync

以Spring boot 为例,启动类中增加@EnableAsync:

@EnableAsync// 开启异步调用@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class, args);}}
2、方法上加@Async注解
@Component//  添加注解表示这个类本身要被Spring管理publicclassMyAsyncTask{/**
    * 异步方法
    * 默认情况下,Spring 使用 SimpleAsyncTaskExecutor 去执行这些异步方法(此执行器没有限制线程数)。
    * 此默认值可以从两个层级进行覆盖:
    * 方法级别
    * 应用级别
    */@Async// 添加注解表示这个方法要异步执行publicvoidtestSync(){TimeUnit.SECONDS.sleep(10);System.out.println(Thread.currentTread().getName()+":testSync");}}

Thread.currentTread().getName():http-nio-8086-exec-1:testSync

3、测试
@ServicepublicclassTestService{@AutowiredprivateMyAsyncTask myAsyncTask;publicStringsync(){// 调用MyAsyncTask类的异步方法testSync
        myAsyncTask.testSync();return"sync";}}

正常情况下,执行testSync 方法会阻塞10秒。但是,可以看到,sync 方法调用 testSync 后直接立即返回,并没有等待testSync 方法阻塞10秒,这就是异步效果。

其原理是,在默认情况下开启了一个线程前缀为 task-x 的新线程在执行任务

三、异步任务的返回结果

点开@Async 注解源码,可以看到这样一句话:
在这里插入图片描述

In terms of target method signatures, any parameter types are supported. However, the return type is constrained to either void or java.util.concurrent.Future.

翻译:
在目标方法(说到目标方法,说到 target,说明存在一个代理对象)的签名中,入参是任何类型都支持的。但是,返回类型被限制为 void 或者 Future

所以,异步方法的返回值只能有两种:void 或者 Future。
我们有时需要异步方法返回结果,有时不需要。
不需要返回结果的比较简单,和上面的示例一样,就不多说了。
需要接收返回结果的示例如下:

// 异步方法@AsyncpublicFuture<Map<Long,List>>queryMap(List ids){List<> result = businessService.queryMap(ids);..............Map<Long,List> resultMap =Maps.newHashMap();...returnnewAsyncResult<>(resultMap);}

调用异步方法的示例:

publicMap<Long,List>asyncProcess(List<BindDeviceDO> bindDevices,List<BindStaffDO> bindStaffs,String dccId){Map<Long,List> finalMap =null;// 返回值:Future<Map<Long,List>> asyncResult =MyService.queryMap(ids);try{
            finalMap = asyncResult.get();}catch(Exception e){...}return finalMap;}

可以看到,其实返回值是可以返回任何类型的,只是说,需要用 java.util.concurrent.Future类来包装一下, 然后结果可以通过Future类的get方法获取。Future:这里V可以是任何类型。

四、自定义线程池

点开@Async注解源码,可以看到,只有1个value 属性
在这里插入图片描述
这个value属性就是用来传线程池的bean名称的,相当于指定线程池的意思。

上面我们提到了如果@Async不指定任何value值,那么Spring 使用默认的线程池/执行器 去执行这些异步方法,这个默认的执行器就是:SimpleAsyncTaskExecutor

这个执行器有什么特征呢?

默认核心线程数:8,
最大线程数:Integer.MAX_VALUE,
队列使用 LinkedBlockingQueue,
容量是:Integer.MAX_VALUE,
空闲线程保留时间:60s,
线程池拒绝策略:AbortPolicy

最大线程数是Integer的最大值,队列使用的是无界队列,从最大线程数和队列来看,并发情况下,这个默认线程池是有内存溢出风险的。

所以,如果业务并发量大,我们最好使用自定义线程池

如何自定义线程池,步骤如下:

1、配置文件自定义配置参数
spring:task:execution:pool:# 最大线程数max-size:10# 核心线程数core-size:5# 空闲线程存活时间keep-alive: 5s
        # 队列长度queue-capacity:1000# 线程名前缀thread-name-prefix: task_name
2、编写配置类
@Configuration@DatapublicclassExecutorConfig{/**
     * 核心线程
     */privateint corePoolSize;/**
     * 最大线程
     */privateint maxPoolSize;/**
     * 队列容量
     */privateint queueCapacity;/**
     * 保持时间
     */privateint keepAliveSeconds;/**
     * 名称前缀
     */privateString preFix;@Bean("MyExecutor")publicExecutormyExecutor(){ThreadPoolTaskExecutor executor =newThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(preFix);
        executor.setRejectedExecutionHandler(newThreadPoolExecutor.AbortPolicy());
        executor.initialize();return executor;}}
3、异步方法中引用
@ComponentpublicclassMyAsyncTask{@Async("MyExecutor")//使用自定义的线程池(执行器)publicvoidasyncCpsItemImportTask(Long platformId,String jsonList){//...具体业务逻辑}}

五、注意事项

1、无法调用同类中的@Async的方法

@Async 的原理是通过 Spring AOP 动态代理 的方式来实现的,如果a方法调用它同类中的标注@Async的b方法,是不会异步执行的,因为从a方法进入调用的都是该类对象本身,不会进入代理类。
因此,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步。

@ServicepublicclassTestService{publicStringsync(){// 调用同类中的异步方法testSync,异步无效果testSync();return"sync";}/**
   * 异步方法 testSync
   */@AsyncpublicvoidtestSync(){TimeUnit.SECONDS.sleep(10);System.out.println("testSync");}}
2、异步方法是static方法,@Async注解无效果
/**
   * 异步方法不能是 static 方法,不然注解失效
   */@Asyncpublicstaticvoidtest3(){try{
      log.info(Thread.currentThread().getName()+" in test3, before sleep.");Thread.sleep(2000);
      log.info(Thread.currentThread().getName()+" in test3, after sleep.");}catch(InterruptedException e){
      log.error("sleep error.");}}
标签: spring java Async

本文转载自: https://blog.csdn.net/u012660464/article/details/128286690
版权归原作者 格子衫111 所有, 如有侵权,请联系我们删除。

“Spring中@Async注解的使用”的评论:

还没有评论