SpringBoot 注解 @Async 用法以及注意事项
一 、@Async 的使用方式介绍
spring中用@Async注解标记的方法,称为异步方法,它会在调用方的当前线程之外的独立的线程中执行,其实就相当于我们自己 new Thread(() -> System.out.println("Hello world !")); 这样在另一个线程中去执行相应的业务逻辑。本篇先只讲@Async的使用,以后再分析它的实现原理。
@Async注解使用条件
1、@Async注解一般用在类的方法上,如果用在类上,那么这个类所有的方法都是异步执行的;
2、所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;
3、调用异步方法类上需要配置上注解@EnableAsync
使用注意:
1、默认情况下(即@EnableAsync注解的mode=AdviceMode.PROXY),同一个类内部没有使用@Async注解修饰的方法调用@Async注解修饰的方法,是不会异步执行的,这点跟 @Transitional 注解类似,底层都是通过动态代理实现的。如果想实现类内部自调用也可以异步,则需要切换@EnableAsync注解的mode=AdviceMode.ASPECTJ,详见@EnableAsync注解。
2、任意参数类型都是支持的,但是方法返回值必须是void或者Future类型。当使用Future时,你可以使用 实现了Future接口的ListenableFuture接口或者CompletableFuture类与异步任务做更好的交互。如果异步方法有返回值,没有使用Future类型的话,调用方获取不到返回值。
1、 了解 @Async
在 java 应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3.x之后,就已经内置了@Async来完美解决这个问题,本文将完成介绍@Async的用法。
2、 何为异步调用?
在解释异步调用之前,我们先来看同步调用的定义;同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。
例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕; 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。
3、常规的异步调用处理方式
在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。
4、@Async介绍
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
5、如何在Spring中启用@Async
基于SpringBoot配置的启用方式:在 SpringBoot 的启动类上开启异步注解支持 @EnableAsync
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args){
SpringApplication.run(DemoApplication.class, args);
}
}
基于XML配置文件的启用方式,配置如下:
<task:executor id="myexecutor" pool-size="5" /> <task:annotation-driven executor="myexecutor"/>
6、基于@Async无返回值调用 (多用于此方法)
@Async //标注使用 public void asyncMethodWithVoidReturnType() { System.out.println("开启一个新线程,线程名字叫: " + Thread.currentThread().getName()); }
7、基于@Async 有返回值的调用
二、@Async 的使用注意事项
在实际的项目中,对于一些用时比较长的代码片段或者函数,我们可以采用异步的方式来执行,这样就不会影响整体的流程了。比如我在一个用户请求中需要上传一些文件,但是上传文件的耗时会相对来说比较长,这个时候如果上传文件的成功与否不影响主流程的话,就可以把上传文件的操作异步化,在spring boot中比较常见的方式就是把要异步执行的代码片段封装成一个函数,然后在函数头使用@Async注解,就可以实现代码的异步执行。 让我来复述一下吧!
首先新建一个spring boot项目
package com.example.demo; @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args){ SpringApplication.run(DemoApplication.class, args); } }
新建一个类Task,用来放三个异步任务doTaskOne、doTaskTwo、doTaskThree:
package com.example.demo.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.Random; @Component public class Task { public static Random random = new Random(); @Async public void doTaskOne() throws InterruptedException { System.out.println("开始做任务一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任务一,耗时:"+ (end - start)); } @Async public void doTaskTwo() throws Exception { System.out.println("开始做任务二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:" + (end - start) + "毫秒"); } @Async public void doTaskThree() throws InterruptedException { System.out.println("开始做任务三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任务三,耗时:" + (end - start) + "毫秒"); } }
为了让这三个方法执行完,我们需要再单元测试用例上的最后一行加上一个延时,不然等函数退出了,异步任务还没执行完。
在主启动类上测试,测试这三个方法的执行过程:
package com.example.demo; import com.example.demo.service.UseAsync; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) throws Exception { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); Task task = context.getBean("task", Task.class); task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); Thread.sleep(10000); System.out.println("血魔"); } }
看看我随意的几次测试效果:
我们看到三个任务确实是异步执行的,那我们再看看错误的使用方法。
在调用这些异步方法的类中写一个调用这些被@Async注释方法的方法。
package com.example.demo.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.Random; @Component public class Task { public static Random random = new Random(); public void useAsync() throws Exception { doTaskOne(); doTaskTwo(); doTaskThree(); } @Async public void doTaskOne() throws InterruptedException { System.out.println("开始做任务一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任务一,耗时:"+ (end - start)); } @Async public void doTaskTwo() throws Exception { System.out.println("开始做任务二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任务二,耗时:" + (end - start) + "毫秒"); } @Async public void doTaskThree() throws InterruptedException { System.out.println("开始做任务三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任务三,耗时:" + (end - start) + "毫秒"); } }
然后在启动类调用 这个方法
package com.example.demo; import com.example.demo.service.Task; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) throws Exception { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); Task task = context.getBean("task", Task.class); task.useAsync(); Thread.sleep(5000); System.out.println("血魔"); } }
然后我们来看看这个方法的执行结果:(多实验几次)
可以看出这些方法并没用异步执行,所以 这种在类中调用 本类中被@Async 修饰的方法就不会被异步执行, 所以 @Async 方法尽量放在单独的类中,而不要挤在 冗余的代码中。