詳解SpringBoot中的異步調(diào)用@Async
如何開啟異步調(diào)用
在SpringBoot中,只需要給方法加上@Async注解,就能將同步方法變?yōu)楫惒秸{(diào)用。
首先在啟動(dòng)類上添加@EnableAsync,即開啟異步調(diào)用。
- /**
 - * @author qcy
 - */
 - @SpringBootApplication
 - @EnableAsync
 - public class AsyncApplication {
 - public static void main(String[] args) {
 - SpringApplication.run(AsyncApplication.class, args);
 - }
 - }
 
在需要異步調(diào)用的方法上加上@Async注解
- package com.yang.async;
 - import lombok.extern.slf4j.Slf4j;
 - import org.springframework.scheduling.annotation.Async;
 - import org.springframework.scheduling.annotation.AsyncResult;
 - import org.springframework.stereotype.Component;
 - import java.util.concurrent.Future;
 - import java.util.concurrent.FutureTask;
 - /**
 - * @author qcy
 - * @create 2020/09/09 14:01:35
 - */
 - @Slf4j
 - @Component
 - public class Task {
 - @Async
 - public void method1() {
 - log.info("method1開始,執(zhí)行線程為" + Thread.currentThread().getName());
 - try {
 - Thread.sleep(2000);
 - } catch (InterruptedException e) {
 - e.printStackTrace();
 - }
 - log.info("method1結(jié)束");
 - }
 - @Async
 - public void method2() {
 - log.info("method2開始,執(zhí)行線程為" + Thread.currentThread().getName());
 - try {
 - Thread.sleep(3000);
 - } catch (InterruptedException e) {
 - e.printStackTrace();
 - }
 - log.info("method2結(jié)束");
 - }
 - }
 
測試一下:
- @SpringBootTest
 - @Slf4j
 - public class AsyncApplicationTests {
 - @Autowired
 - Task task;
 - @Test
 - public void testAsyncWithVoidReturn() throws InterruptedException {
 - log.info("main線程開始");
 - task.method1();
 - task.method2();
 - //確保兩個(gè)異步調(diào)用執(zhí)行完成
 - Thread.sleep(6000);
 - log.info("main線程結(jié)束");
 - }
 - }
 
輸出如下:
可以看得出,SpringBoot創(chuàng)建了一個(gè)名為applicationTaskExecutor的線程池,使用這里面的線程來執(zhí)行異步調(diào)用。
這里值得注意的是,不要在一個(gè)類中調(diào)用@Async標(biāo)注的方法,否則不會(huì)起到異步調(diào)用的作用,至于為什么會(huì)產(chǎn)生這樣的問題,需要深入到源碼中一探究竟,會(huì)另開篇幅。
既然默認(rèn)使用的是SpringBoot自己創(chuàng)建的applicationTaskExecutor,那如何自己去定義一個(gè)線程池呢?
自定義線程池
我們需要手動(dòng)創(chuàng)建一個(gè)名為asynTaskExecutord的Bean
- package com.yang.async;
 - import lombok.extern.slf4j.Slf4j;
 - import org.springframework.context.annotation.Bean;
 - import org.springframework.context.annotation.Configuration;
 - import org.springframework.core.task.AsyncTaskExecutor;
 - import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 - import java.util.concurrent.ThreadPoolExecutor;
 - /**
 - * @author qcy
 - * @create 2020/09/09 15:31:07
 - */
 - @Slf4j
 - @Configuration
 - public class AsyncConfig {
 - @Bean
 - public AsyncTaskExecutor asyncTaskExecutor() {
 - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 - executor.setCorePoolSize(8);
 - executor.setMaxPoolSize(16);
 - executor.setQueueCapacity(50);
 - executor.setAllowCoreThreadTimeOut(true);
 - executor.setKeepAliveSeconds(10);
 - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 - executor.setThreadNamePrefix("async-thread-pool-thread");
 - return executor;
 - }
 - }
 
對以上參數(shù)不了解的同學(xué),可以參考我的這篇文章說說線程池
其他類不需要變動(dòng),直接運(yùn)行剛才的testAsyncWithVoidReturn()方法,輸出:
看得出來,現(xiàn)在是我們自定義的線程池
如果關(guān)心異步調(diào)用的返回值,又怎么處理?
獲取異步調(diào)用的返回結(jié)果
獲取異步調(diào)用的結(jié)果,需要利用Future機(jī)制,可以參考我的另外一篇文章談?wù)凴unnable、Future、Callable、FutureTask之間的關(guān)系
為Task類增加以下兩個(gè)方法:
- @Async
 - public Future<String> method3() {
 - log.info("method3開始,執(zhí)行線程為" + Thread.currentThread().getName());
 - try {
 - Thread.sleep(1000);
 - } catch (InterruptedException e) {
 - e.printStackTrace();
 - }
 - log.info("method3結(jié)束");
 - return new AsyncResult<>("method3");
 - }
 - @Async
 - public Future<String> method4() {
 - log.info("method4開始,執(zhí)行線程為" + Thread.currentThread().getName());
 - try {
 - Thread.sleep(3000);
 - } catch (InterruptedException e) {
 - e.printStackTrace();
 - }
 - log.info("method4結(jié)束");
 - return new AsyncResult<>("method4");
 - }
 
測試類:
- @Test
 - public void testAsyncWithStringReturn() throws InterruptedException, ExecutionException {
 - log.info("main線程開始");
 - Future<String> method3Result = task.method3();
 - Future<String> method4Result = task.method4();
 - //get方法為阻塞獲取
 - log.info("method3執(zhí)行的返回結(jié)果:{}", method3Result.get());
 - log.info("method4執(zhí)行的返回結(jié)果:{}", method4Result.get());
 - log.info("main線程結(jié)束");
 - }
 
輸出:
如圖,在主線程結(jié)束前,獲取到了異步調(diào)用的結(jié)果。且在兩個(gè)異步調(diào)用都結(jié)束的情況下,繼續(xù)執(zhí)行主線程。


















 
 
 





 
 
 
 