编程知识 cdmana.com

SpringBoot异步使用@Async原理及線程池配置

前言

在實際項目開發中很多業務場景需要使用异步去完成,比如消息通知,日志記錄,等非常常用的都可以通過异步去執行,提高效率,那麼在Spring框架中應該如何去使用异步呢

使用步驟

完成异步操作一般有兩種,消息隊列MQ,和線程池處理ThreadPoolExecutor

而在Spring4中提供的對ThreadPoolExecutor封裝的線程池ThreadPoolTaskExecutor,直接使用注解啟用@Async,這個注解讓我們在使用Spring完成异步操作變得非常方便

配置線程池類參數配置

自定義常量類

public class ConstantFiledUtil {    public static final String AUTHORIZATION_TOKEN = "authorizationToken";    /**     * kmall線程池名稱     */    public static final String KMALL_THREAD_POOL = "KmallThreadPool";    /**     * kmall線程名稱前綴     */    public static final String KMALL_THREAD_NAME_PREFIX = "kmall-thread-";}

配置線程池

@Configuration(proxyBeanMethods = false)@EnableAsync //開啟注解public class KmallConfig {  @Bean(ConstantFiledUtil.KMALL_THREAD_POOL)    public ThreadPoolTaskExecutor FebsShiroThreadPoolTaskExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();         //配置核心線程數        executor.setCorePoolSize(5);        //配置最大線程數        executor.setMaxPoolSize(20);        //配置隊列大小        executor.setQueueCapacity(200);        //線程池維護線程所允許的空閑時間        executor.setKeepAliveSeconds(30);        //配置線程池中的線程的名稱前綴        executor.setThreadNamePrefix(ConstantFiledUtil.KMALL_THREAD_NAME_PREFIX);        //設置線程池關閉的時候等待所有任務都完成再繼續銷毀其他的Bean        executor.setWaitForTasksToCompleteOnShutdown(true);        //設置線程池中任務的等待時間,如果超過這個時候還沒有銷毀就强制銷毀,以確保應用最後能够被關閉,而不是阻塞住        executor.setAwaitTerminationSeconds(60);          // rejection-policy:當pool已經達到max size的時候,如何處理新任務        // CALLER_RUNS:不在新線程中執行任務,而是由調用者所在的線程來執行        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        //執行初始化        executor.initialize();        return executor;    }}

注意這裏需要通過@EnableAsync開啟异步否則無效

自定義線程任務

public interface ILogService extends IService<Log> {    /**     * 分頁搜索查詢日志信息     * @param logParams     * @return     */    IPage getSearchLogByPage(SearchLogParams logParams);    /**     * 保存日志     *     * @param logSubject     */    @Async    void saveLog(LogSubject logSubject);}

在需要异步執行的接口,或者方法上加上@Async注解此方法就是异步方法,在主程序中調用的化,就是异步方式單獨線程執行

此注解錶明saveLog方法進入的線程池是KmallThreadPool方法創建的。

我們也可以單獨指定方法名稱@Async("saveLogs")

這樣在進行日記記錄的時候就是單獨線程執行每次請求都快速響應了,而耗時的操作都留給線程池中的線程去异步執行

總結

Spring中用@Async注解標記的方法,稱為异步方法。在spring boot應用中使用@Async很簡單:

  1. 調用异步方法類上或者啟動類加上注解@EnableAsync
  2. 在需要被异步調用的方法外加上@Async
  3. 所使用的@Async注解方法的類對象應該是Spring容器管理的bean對象;

注意同一個類裏面調用异步方法不生效:原因默認類內的方法調用不會被aop攔截,即調用方和被調用方是在同一個類中,是無法產生切面的,該對象沒有被Spring容器管理。即@Async方法不生效

解决辦法:

如果要使同一個類中的方法之間調用也被攔截,需要使用spring容器中的實例對象,而不是使用默認的this,因為通過bean實例的調用才會被spring的aop攔截本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然後使用這個引用調用本地的方法即可達到被攔截的目的備注:這種方法只能攔截protected,default,public方法,private方法無法攔截。這個是spring aop的一個機制。

原理刨析

异步方法返回類型只能有兩種:

  1. 當返回類型為void的時候,方法調用過程產生的异常不會拋到調用者層面,可以通過注AsyncUncaughtExceptionHandler來捕獲此類异常

  2. 當返回類型為Future的時候,方法調用過程產生的异常會拋到調用者層面

注意如果不自定義异步方法的線程池默認使用SimpleAsyncTaskExecutorSimpleAsyncTaskExecutor:不是真的線程池,這個類不重用線程,每次調用都會創建一個新的線程。並發大的時候會產生嚴重的性能問題

Spring异步線程池接口 TaskExecutor

看源碼可知

@FunctionalInterfacepublic interface TaskExecutor extends Executor {    void execute(Runnable var1);}

它的實先類有很多如下:

SpringBoot异步使用@Async原理及線程池配置

  1. SimpleAsyncTaskExecutor:不是真的線程池,這個類不重用線程,每次調用都會創建一個新的線程。

  2. SyncTaskExecutor:這個類沒有實現异步調用,只是一個同步操作。只適用於不需要多線程的地方

  3. ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類

  4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的類。線程池同時被quartz和非quartz使用,才需要使用此類

  5. ThreadPoolTaskExecutor :最常使用,推薦。 其實質是對java.util.concurrent.ThreadPoolExecutor的包裝,————————————————

文章參考

  1. Spring Boot(5) @Async异步線程池詳解
  2. https://segmentfault.com/a/1190000040134160?utm_source=sf-similar-article

版权声明
本文为[kenx]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/09/20210915071141180Z.html

Scroll to Top