天天看點

SpringBoot中的異步調用@Async

如何開啟異步調用

在SpringBoot中,隻需要給方法加上@Async注解,就能将同步方法變為異步調用。

首先在啟動類上添加@EnableAsync,即開啟異步調用。

/**
 * @author qcy
 */
@SpringBootApplication
@EnableAsync
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}      

在需要異步調用的方法上加上@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開始,執行線程為" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("method1結束");
    }

    @Async
    public void method2() {
        log.info("method2開始,執行線程為" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("method2結束");
    }


}      

測試一下:

@SpringBootTest
@Slf4j
public class AsyncApplicationTests {

    @Autowired
    Task task;

    @Test
    public void testAsyncWithVoidReturn() throws InterruptedException {
        log.info("main線程開始");

        task.method1();
        task.method2();

        //確定兩個異步調用執行完成
        Thread.sleep(6000);

        log.info("main線程結束");
    }

}      

輸出如下:

SpringBoot中的異步調用@Async

可以看得出,SpringBoot建立了一個名為applicationTaskExecutor的線程池,使用這裡面的線程來執行異步調用。

這裡值得注意的是,不要在一個類中調用@Async标注的方法,否則不會起到異步調用的作用,至于為什麼會産生這樣的問題,需要深入到源碼中一探究竟,會另開篇幅。

既然預設使用的是SpringBoot自己建立的applicationTaskExecutor,那如何自己去定義一個線程池呢?

自定義線程池

我們需要手動建立一個名為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;
    }
}      

對以上參數不了解的同學,可以參考我的這篇文章​​說說線程池​​

其他類不需要變動,直接運作剛才的testAsyncWithVoidReturn()方法,輸出:

SpringBoot中的異步調用@Async

看得出來,現在是我們自定義的線程池

如果關心異步調用的傳回值,又怎麼處理?

擷取異步調用的傳回結果

擷取異步調用的結果,需要利用Future機制,可以參考我的另外一篇文章​​談談Runnable、Future、Callable、FutureTask之間的關系​​

為Task類增加以下兩個方法:

@Async
    public Future<String> method3() {
        log.info("method3開始,執行線程為" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("method3結束");
        return new AsyncResult<>("method3");
    }

    @Async
    public Future<String> method4() {
        log.info("method4開始,執行線程為" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("method4結束");
        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執行的傳回結果:{}", method3Result.get());
        log.info("method4執行的傳回結果:{}", method4Result.get());
        log.info("main線程結束");
    }      

繼續閱讀