SpringBoot-異步調用@Async


除了異步請求,一般上我們用的比較多的應該是異步調用。通常在開發過程中,會遇到一個方法是和實際業務無關的,沒有緊密性的。比如記錄日志信息等業務。這個時候正常就是啟一個新線程去做一些業務處理,讓主線程異步的執行其他業務。

異步調用?

通常開發過程中,一般上我們都是同步調用,即:程序按定義的順序依次執行的過程,每一行代碼執行過程必須等待上一行代碼執行完畢后才執行。而異步調用指:程序在執行時,無需等待執行的返回值可繼續執行后面的代碼。顯而易見,同步有依賴相關性,而異步沒有,所以異步可並發執行,可提高執行效率,在相同的時間做更多的事情。

代碼

SpringBoot啟動類加上@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class Application {

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

編寫同步調用和異步調用方法

@Async
public void asyncEvent() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async---{}", System.currentTimeMillis());
}

@Override
public void syncEvent() throws InterruptedException{
    Thread.sleep(1000);
    log.info("sync---{}", System.currentTimeMillis());
}

編寫Controller調用

@Autowired
private AsyncService asyncService;

@GetMapping("async")
public Object doAsync() throws InterruptedException {
    long startTime = System.currentTimeMillis();
    log.info("方法開始執行---{}", startTime);

    asyncService.syncEvent();
    long syncTime = System.currentTimeMillis();
    log.info("同步方法執行用時--{}", syncTime - startTime);

    asyncService.asyncEvent();
    long asyncTime = System.currentTimeMillis();
    log.info("異步方法執行用時--{}", asyncTime - syncTime);

    log.info("方法執行結束--{}", System.currentTimeMillis());
    return "success";
}

調用打印日志為

2019-12-24 07:46:50.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法開始執行---1577144810560
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.a.async.service.impl.AsyncServiceImpl  : sync---1577144811560
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 同步方法執行用時--1000
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 異步方法執行用時--0
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法執行結束--1577144811560
2019-12-24 07:46:52.561  INFO 8904 --- [         task-3] c.a.async.service.impl.AsyncServiceImpl  : async---1577144812561

注:在默認情況下,未設置TaskExecutor時,默認是使用SimpleAsyncTaskExecutor這個線程池,但此線程不是真正意義上的線程池,因為線程不重用,每次調用都會創建一個新的線程。可通過控制台日志輸出可以看出,每次輸出線程名都是遞增的。
調用的異步方法,不能為同一個類的方法,簡單來說,因為Spring在啟動掃描時會為其創建一個代理類,而同類調用時,還是調用本身的代理類的,所以和平常調用是一樣的。其他的注解如@Cache等也是一樣的道理,說白了,就是Spring的代理機制造成的。

如果在同一類的方法加上了異步調用注解

@GetMapping("asyncInOne")
public Object doAsyncInOne() throws InterruptedException {
    long startTime = System.currentTimeMillis();
    log.info("方法開始執行---{}", startTime);

    asyncService.syncEvent();
    long syncTime = System.currentTimeMillis();
    log.info("同步方法執行用時--{}", syncTime - startTime);

    asyncEventInOne();
    long asyncTime = System.currentTimeMillis();
    log.info("異步方法執行用時--{}", asyncTime - syncTime);

    log.info("方法執行結束--{}", System.currentTimeMillis());
    return "success";
}

@Async
public void asyncEventInOne() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async-controller--{}", System.currentTimeMillis());

}

此時打印日志為:(可以看出並沒有進行異步調用)

2019-12-24 07:51:43.550  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法開始執行---1577145103550
2019-12-24 07:51:44.551  INFO 11724 --- [nio-8080-exec-3] c.a.async.service.impl.AsyncServiceImpl  : sync---1577145104551
2019-12-24 07:51:44.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 同步方法執行用時--1001
2019-12-24 07:51:45.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : async-controller--1577145105551
2019-12-24 07:51:45.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 異步方法執行用時--1000
2019-12-24 07:51:45.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法執行結束--1577145105551

自定義線程池

在默認情況下,系統使用的是默認的SimpleAsyncTaskExecutor進行線程創建。所以一般上我們會自定義線程池來進行線程的復用。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadConfig {

    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncTheadPoolExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("oKong-");
        //線程池對拒絕任務(無可用線程的處理策略,目前只支持AbortPolicy、CallerRunPolicy:默認為后者)
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

------------------------
corePoolSize:線程池維護線程的最少數量
keepAliveSeconds:允許的空閑時間,當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀
maxPoolSize:線程池維護線程的最大數量,只有在緩沖隊列滿了之后才會申請超過核心線程數的線程
queueCapacity:緩存隊列
rejectedExecutionHandler:線程池對拒絕任務(無線程可用)的處理策略。這里采用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute 方法的調用線程中運行被拒絕的任務;如果執行程序已關閉,則會丟棄該任務。還有一個是AbortPolicy策略:處理程序遭到拒絕將拋出運行時RejectedExecutionException。
而在一些場景下,若需要在關閉線程池時等待當前調度任務完成后才開始關閉,可以通過簡單的配置,進行優雅的停機策略配置。關鍵就是通過setWaitForTasksToCompleteOnShutdown(true)和setAwaitTerminationSeconds方法。
setWaitForTasksToCompleteOnShutdown:表明等待所有線程執行完,默認為false。
setAwaitTerminationSeconds:等待的時間,因為不能無限的等待下去。
------------------------

異步注解加上我們的線程池名稱

@Async("asyncPoolTaskExecutor")
public void asyncEvent() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async---{}", System.currentTimeMillis());
    log.info("async---線程名稱{}", Thread.currentThread().getName());
}

此視再看打印的日志

2019-12-24 08:52:04.704  INFO 17824 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-24 08:52:04.704  INFO 17824 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-12-24 08:52:04.709  INFO 17824 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
2019-12-24 08:52:04.727  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 方法開始執行---1577148724727
2019-12-24 08:52:05.728  INFO 17824 --- [nio-8080-exec-1] c.a.async.service.impl.AsyncServiceImpl  : sync---1577148725728
2019-12-24 08:52:05.728  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 同步方法執行用時--1001
2019-12-24 08:52:05.731  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 異步方法執行用時--3
2019-12-24 08:52:05.731  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 方法執行結束--1577148725731
2019-12-24 08:52:06.732  INFO 17824 --- [         task-1] c.a.async.service.impl.AsyncServiceImpl  : async---1577148726732
2019-12-24 08:52:06.732  INFO 17824 --- [         task-1] c.a.async.service.impl.AsyncServiceImpl  : async---線程名稱task-1
2019-12-24 08:52:11.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法開始執行---1577148731910
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.a.async.service.impl.AsyncServiceImpl  : sync---1577148732910
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 同步方法執行用時--1000
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 異步方法執行用時--0
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法執行結束--1577148732910
2019-12-24 08:52:13.910  INFO 17824 --- [         task-2] c.a.async.service.impl.AsyncServiceImpl  : async---1577148733910
2019-12-24 08:52:13.910  INFO 17824 --- [         task-2] c.a.async.service.impl.AsyncServiceImpl  : async---線程名稱task-2
2019-12-24 08:52:18.368  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法開始執行---1577148738368
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.a.async.service.impl.AsyncServiceImpl  : sync---1577148739369
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 同步方法執行用時--1001
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 異步方法執行用時--0
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法執行結束--1577148739369
2019-12-24 08:52:20.369  INFO 17824 --- [         task-3] c.a.async.service.impl.AsyncServiceImpl  : async---1577148740369
2019-12-24 08:52:20.369  INFO 17824 --- [         task-3] c.a.async.service.impl.AsyncServiceImpl  : async---線程名稱task-3

異步回調與超時

對於一些業務場景下,需要異步回調的返回值時,就需要使用異步回調來完成了。主要就是通過Future進行異步回調。

@Async
public Future<String> asyncEventWithReturn() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async---{}", System.currentTimeMillis());
    log.info("async---線程名稱{}", Thread.currentThread().getName());
    return new AsyncResult<>("異步方法返回");
}

其中AsyncResult是Spring提供的一個Future接口的子類。
然后通過isDone方法,判斷是否已經執行完畢。

@GetMapping("asyncWithReturn")
public Object doAsyncWithReturn() throws InterruptedException {
    long startTime = System.currentTimeMillis();
    log.info("方法開始執行---{}", startTime);

    asyncService.syncEvent();
    long syncTime = System.currentTimeMillis();
    log.info("同步方法執行用時--{}", syncTime - startTime);

    Future<String> stringFuture = asyncService.asyncEventWithReturn();
    while (true) {
        if (stringFuture.isDone()) {
            break;
        }
    }
    long asyncTime = System.currentTimeMillis();
    log.info("異步方法執行用時--{}", asyncTime - syncTime);

    log.info("方法執行結束--{}", System.currentTimeMillis());
    return "success";
}

調用結果為

2019-12-24 09:26:28.835  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法開始執行---1577150788835
2019-12-24 09:26:29.837  INFO 5492 --- [nio-8080-exec-2] c.a.async.service.impl.AsyncServiceImpl  : sync---1577150789837
2019-12-24 09:26:29.837  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 同步方法執行用時--1002
2019-12-24 09:26:30.840  INFO 5492 --- [        oKong-1] c.a.async.service.impl.AsyncServiceImpl  : async---1577150790840
2019-12-24 09:26:30.840  INFO 5492 --- [        oKong-1] c.a.async.service.impl.AsyncServiceImpl  : async---線程名稱oKong-1
2019-12-24 09:26:30.841  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 異步方法執行用時--1004
2019-12-24 09:26:30.841  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法執行結束--1577150790841

對於一些需要異步回調的函數,不能無期限的等待下去,所以一般上需要設置超時時間,超時后可將線程釋放,而不至於一直堵塞而占用資源。

try {
    stringFuture.get(100, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (TimeoutException e) {
    log.error("超時");
    e.printStackTrace();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM