前言
上一章节,我们知道了如何进行异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。所以,本章节重点说下在
SpringBoot
中如何进行异步调用及其相关知识和注意点。
一点知识
何为异步调用
说异步调用
前,我们说说它对应的同步调用
。通常开发过程中,一般上我们都是同步调用
,即:程序按定义的顺序依次执行的过程,每一行代码执行过程必须等待上一行代码执行完毕后才执行。而异步调用
指:程序在执行时,无需等待执行的返回值可继续执行后面的代码。显而易见,同步有依赖相关性,而异步没有,所以异步可并发
执行,可提高执行效率,在相同的时间做更多的事情。
题外话:处理异步
、同步
外,还有一个叫回调
。其主要是解决异步方法执行结果的处理方法,比如在希望异步调用结束时返回执行结果,这个时候就可以考虑使用回调机制。
Async异步调用
在
SpringBoot
中使用异步调用是很简单的,只需要使用@Async
注解即可实现方法的异步调用。
注意:需要在启动类加入@EnableAsync
使异步调用@Async
注解生效。
@SpringBootApplication @EnableAsync @Slf4j public class Chapter21Application { public static void main(String[] args) { SpringApplication.run(Chapter21Application.class, args); log.info("Chapter21启动!"); } }
@Async异步调用
使用@Async
很简单,只需要在需要异步执行的方法上加入此注解即可。这里创建一个控制层和一个服务层,进行简单示例下。
SyncService.java
@Component public class SyncService { @Async public void asyncEvent() throws InterruptedException { //休眠1s Thread.sleep(1000); //log.info("异步方法输出:{}!", System.currentTimeMillis()); } public void syncEvent() throws InterruptedException { Thread.sleep(1000); //log.info("同步方法输出:{}!", System.currentTimeMillis()); } }
控制层:AsyncController.java
@RestController @Slf4j public class AsyncController { @Autowired SyncService syncService; @GetMapping("/async") public String doAsync() throws InterruptedException { long start = System.currentTimeMillis(); log.info("方法执行开始:{}", start); //调用同步方法 syncService.syncEvent(); long syncTime = System.currentTimeMillis(); log.info("同步方法用时:{}", syncTime - start); //调用异步方法 syncService.asyncEvent(); long asyncTime = System.currentTimeMillis(); log.info("异步方法用时:{}", asyncTime - syncTime); log.info("方法执行完成:{}!",asyncTime); return "async!!!"; } }
应用启动后,可以看见控制台输出:
2018-08-16 22:21:35.949 INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController : 方法执行开始:1534429295949 2018-08-16 22:21:36.950 INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController : 同步方法用时:1001 2018-08-16 22:21:36.950 INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController : 异步方法用时:0 2018-08-16 22:21:36.950 INFO 17152 --- [nio-8080-exec-5] c.l.l.s.c.controller.AsyncController : 方法执行完成:1534429296950! 2018-08-16 22:21:37.950 INFO 17152 --- [cTaskExecutor-3] c.l.l.s.chapter21.service.SyncService : 异步方法内部线程名称:SimpleAsyncTaskExecutor-3!
可以看出,调用异步方法时,是立即返回的,基本没有耗时。
这里有几点需要注意下:
- 在默认情况下,未设置
TaskExecutor
时,默认是使用SimpleAsyncTaskExecutor
这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。 - 调用的异步方法,不能为
同一个类
的方法,简单来说,因为Spring
在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache
等也是一样的道理,说白了,就是Spring
的代理机制造成的。
自定义线程池
前面有提到,在默认情况下,系统使用的是默认的
SimpleAsyncTaskExecutor
进行线程创建。所以一般上我们会自定义线程池来进行线程的复用。
创建一个自定义的ThreadPoolTaskExecutor
线程池:
Config.java
@Configuration public class Config { /** * 配置线程池 * @return */ @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() { ThreadPoolTaskExecut