Spring中@Async注解的使用

Posted 格子衫111

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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  // 开启异步调用
@SpringBootApplication
public class Application
    public static void main(String[] args) 
        SpringApplication.run(Application.class, args);
    

2、方法上加@Async注解

@Component //  添加注解表示这个类本身要被Spring管理
public class MyAsyncTask 
	/**
	* 异步方法
	* 默认情况下,Spring 使用 SimpleAsyncTaskExecutor 去执行这些异步方法(此执行器没有限制线程数)。
	* 此默认值可以从两个层级进行覆盖:
	* 方法级别
	* 应用级别
	*/
     @Async  // 添加注解表示这个方法要异步执行
    public void testSync() 
        TimeUnit.SECONDS.sleep(10);
        System.out.println("testSync");
    

3、测试
@Service
public class TestService 
	
	@Autowired
    private MyAsyncTask myAsyncTask;

    public String sync() 
    	// 调用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。
我们有时需要异步方法返回结果,有时不需要。
不需要返回结果的比较简单,和上面的示例一样,就不多说了。
需要接收返回结果的示例如下:

// 异步方法
@Async
public Future<Map<Long, List>> queryMap(List ids) 
    List<> result = businessService.queryMap(ids);
    ..............
    Map<Long, List> resultMap = Maps.newHashMap();
    ...
    return new AsyncResult<>(resultMap);

调用异步方法的示例:

public Map<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
@Data
public class ExecutorConfig
    /**
     * 核心线程
     */
    private int corePoolSize;
    /**
     * 最大线程
     */
    private int maxPoolSize;
    /**
     * 队列容量
     */
    private int queueCapacity;
    /**
     * 保持时间
     */
    private int keepAliveSeconds;
    /**
     * 名称前缀
     */
    private String preFix;
 
    @Bean("MyExecutor")
    public Executor myExecutor() 
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(preFix);
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    

3、异步方法中引用
@Component
public class MyAsyncTask 

     @Async("MyExecutor") //使用自定义的线程池(执行器)
    public void asyncCpsItemImportTask(Long platformId, String jsonList)
        //...具体业务逻辑
    

五、注意事项

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

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

@Service
public class TestService 
  
  
  public String sync() 
    // 调用同类中的异步方法testSync,异步无效果
    testSync();
    return "sync";
  
  
  /**
   * 异步方法 testSync
   */
  @Async 
  public void testSync() 
    TimeUnit.SECONDS.sleep(10);
    System.out.println("testSync");
  


2、异步方法是static方法,@Async注解无效果
  /**
   * 异步方法不能是 static 方法,不然注解失效
   */
  @Async
  public static void test3() 
    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中@Async注解的使用的主要内容,如果未能解决你的问题,请参考以下文章

关于Spring注解@Async引发其他注解失效

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

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

spring中@Async注解的原理和使用

Spring中@Async注解实现“方法”的异步调用

异步任务spring @Async注解源码解析