1.概述
在使用@Async注解之前,首先需要了解同步调用和异步调用的区别。同步调用指的是一个方法内部可能有几个不同的方法,按照从上到下的顺序依次执行。如下图中代码所示,方法init中分别有method1()、method2()、method3()三个方法,这三个方法按照顺序依次执行。异步调用指的是init()方法中的method1()、method2()、method3()执行顺序无明确先后关系,也就是说加上@Async注解后,该方法就变为异步调用。该注解应用的场景主要为:(1)某一方法不需要立即执行,也不需要立即返回执行结果的;(2)该方法运行时间较长;(3)该方法针对的业务场景优先级低。本文将讲述该注解的简单使用方法。
void init() {
void method1(); //方法1
void method2(); //方法2
void method3(); //方法3
}
2.@Async注解的使用
2.1 启动类添加注解@EnableAsync
@EnableDiscoveryClient
@EnableAsync
@EnableFeignClients
@SpringBootApplication
public class StreamQualityProvider {
public static void main(String[] args) {
SpringApplication.run(StreamQualityProvider.class, args);
System.setProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow", "|{}");
}
}
2.2 方法上添加@Async注解
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
@Async
@Override
public void method1() {
log.info("方法执行---------,{}", "method1");
}
@Async
@Override
public void method2() {
log.info("方法执行---------,{}", "method2");
}
@Async
@Override
public void method3() {
log.info("方法执行---------,{}", "method3");
}
@Async
@Override
public String method4() {
log.info("方法执行---------,{}", "method4");
try {
Thread.sleep(1000L);
} catch (Exception e) {
log.error("休眠异常:{}", e);
}
return "method4";
}
@Async
@Override
public String method5() {
log.info("方法执行---------,{}", "method5");
try {
Thread.sleep(3000L);
} catch (Exception e) {
log.error("休眠异常:{}", e);
}
return "method5";
}
@Async
@Override
public String method6() {
log.info("方法执行---------,{}", "method6");
try {
Thread.sleep(5000L);
} catch (Exception e) {
log.error("休眠异常:{}", e);
}
return "method6";
}
@Override
public String method7() {
method1();
method2();
method3();
method4();
return "success";
}
}
2.3 测试
1.异步调用生效
@GetMapping("/test")
public String testMethod1() {
service.method1();
service.method2();
service.method3();
service.method4();
service.method5();
service.method6();
return "success";
}
结果为:
每次方法调用的顺序都可能不一样,是因为异步调用方法无明确先后执行顺序。
2.4 注意事项
1. 调用方法与被@Async修饰的方法不能在同一类中,若在同一类中,则异步方法不生效,会变成同步调用。
测试代码如下:
@PostMapping("/test1")
public String testMethod2() {
return service.method7();
}
结果为:
2.调用方法不能以static关键字修饰,否则不生效。
测试如下:
@Override
public String method9() {
method8();
method10();
method11();
return "success";
}
@Async
public static String method8() {
log.info("方法执行---------,{}", "method8");
try {
Thread.sleep(5000L);
} catch (Exception e) {
log.error("休眠异常:{}", e);
}
return "method8";
}
@Async
public static String method10() {
log.info("方法执行---------,{}", "method10");
try {
Thread.sleep(5000L);
} catch (Exception e) {
log.error("休眠异常:{}", e);
}
return "method8";
}
@Async
public static String method11() {
log.info("方法执行---------,{}", "method11");
try {
Thread.sleep(5000L);
} catch (Exception e) {
log.error("休眠异常:{}", e);
}
return "method11";
}
@PostMapping("/test9")
public String testMethod9() {
return service.method9();
}
结果为:
2.5 多异步方法执行
测试方案如下:
@Async
public Future<String> taskOne() throws Exception {
System.out.println("任务一");
try {
Thread.sleep(1000L);
log.info("线程休眠1s");
}catch (Exception e) {
log.error("Exception:{}",e);
}
return new AsyncResult<>("任务一执行完成");
}
@Async
public Future<String> taskTwo() throws Exception {
try {
Thread.sleep(1000L);
log.info("线程休眠1s");
}catch (Exception e) {
log.error("Exception:{}",e);
}
System.out.println("任务二");
return new AsyncResult<>("任务二执行完成");
}
@Async
public Future<String> taskThree() throws Exception {
try {
Thread.sleep(1000L);
log.info("线程休眠1s");
}catch (Exception e) {
log.error("Exception:{}",e);
}
System.out.println("任务三");
return new AsyncResult<>("任务三执行完成");
}
@PostMapping("/test10")
public String testMethod10() throws Exception {
long startTime = System.currentTimeMillis();
Future<String> taskOne = service.taskOne();
Future<String> taskTwo = service.taskTwo();
Future<String> taskThree = service.taskThree();
int count = 0;
while (true) {
if (taskOne.isDone() && taskTwo.isDone() && taskThree.isDone()) {
log.info("success");
break;
}
try {
Thread.sleep(100L);
log.info("设备休眠:{}次", count++);
} catch (Exception e) {
log.error("Exception:{}", e);
}
}
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
log.info("方法执行时间为:{}",totalTime);
return "方法执行时间为:" + totalTime;
}
结果为:
多异步执行时,这里测试的是三个任务全部执行完成后计算总时间。Future接口可以在异步任务执行完成后返回一个回调函数,只要设置回调信息,就能够判断任务是否正确执行,对于异步函数,添加 Future 返回值类型,使用 new AsyncResult<>() 设置回调信息。当多个方法执行完成拿到结果之后,可以完成接下来的业务逻辑,像上述三个异步方法isDone()都为true之后,可以实现接下来的业务逻辑。
2.6 自定义线程池运行异步方法
@Configuration
public class AsyncConfiguration {
@Bean("asyncExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 线程池创建时候初始化的线程数
executor.setMaxPoolSize(20); // 线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程
executor.setQueueCapacity(200); // 缓冲任务队列的大小
executor.setKeepAliveSeconds(60); // 允许线程的空闲时间,超过会被销毁
executor.setThreadNamePrefix("custom-prefix-");// 线程的前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 对拒绝任务的处理策略
return executor;
}
}
结果为:
3.小结
1.@Async修饰的方法与调用方法不能在同一类中;
2.@Async不能使用在以static关键字修饰的方法上;
3.多异步执行可以定义Future方法来获取执行结果;
4.@Async执行方法的线程可进行自定义设置。
4.参考文献
1.https://juejin.cn/post/6910756747351162894
2.https://juejin.cn/post/6864818743965892622
3.https://juejin.cn/post/6907872876171526157