springboot為異步任務(wù)規(guī)劃自定義線程池的實(shí)現(xiàn)
一、Spring Boot任務(wù)線程池
線程池的作用
- 防止資源占用無限的擴(kuò)張
- 調(diào)用過程省去資源的創(chuàng)建和銷毀所占用的時(shí)間
在高并發(fā)環(huán)境下,不斷的分配新資源,可能導(dǎo)致系統(tǒng)資源耗盡。所以為了避免這個(gè)問題,我們?yōu)楫惒饺蝿?wù)規(guī)劃一個(gè)線程池。當(dāng)然,如果沒有配置線程池的話,springboot會(huì)自動(dòng)配置一個(gè)ThreadPoolTaskExecutor 線程池到bean當(dāng)中。
# 核心線程數(shù) spring.task.execution.pool.core-size=8 # 最大線程數(shù) spring.task.execution.pool.max-size=16 # 空閑線程存活時(shí)間 spring.task.execution.pool.keep-alive=60s # 是否允許核心線程超時(shí) spring.task.execution.pool.allow-core-thread-timeout=true # 線程隊(duì)列數(shù)量 spring.task.execution.pool.queue-capacity=100 # 線程關(guān)閉等待 spring.task.execution.shutdown.await-termination=false spring.task.execution.shutdown.await-termination-period= # 線程名稱前綴 spring.task.execution.thread-name-prefix=task-
在springboot配置文件中加入上面的配置,即可實(shí)現(xiàn)ThreadPoolTaskExecutor 線程池。
二、自定義線程池
有的時(shí)候,我們希望將系統(tǒng)內(nèi)的一類任務(wù)放到一個(gè)線程池,另一類任務(wù)放到另外一個(gè)線程池,所以使用Spring Boot自帶的任務(wù)線程池就捉襟見肘了。下面介紹自定義線程池的方法。
創(chuàng)建一個(gè) 線程池配置類 TaskConfiguration ,并配置一個(gè) 任務(wù)線程池對象 taskExecutor。
@Configuration public class TaskConfiguration { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(new CallerRunsPolicy()); return executor; } }
上面我們通過使用 ThreadPoolTaskExecutor 創(chuàng)建了一個(gè) 線程池,同時(shí)設(shè)置了以下這些參數(shù):
線程池屬性 | 屬性的作用 | 上文代碼設(shè)置初始值 |
---|---|---|
核心線程數(shù)CorePoolSize | 線程池創(chuàng)建時(shí)候初始化的線程數(shù),最小線程數(shù) | 10 |
最大線程數(shù)MaxPoolSize | 線程池最大的線程數(shù),只有在緩沖隊(duì)列滿了之后,才會(huì)申請超過核心線程數(shù)的線程 | 20 |
緩沖任務(wù)隊(duì)列QueueCapacity | 用來緩沖執(zhí)行任務(wù)的隊(duì)列 | 200 |
允許線程的空閑時(shí)間KeepAliveSeconds | 超過了核心線程之外的線程,在空閑時(shí)間到達(dá)之后,沒活干的線程會(huì)被銷毀 | 60秒 |
線程池名的前綴 ThreadNamePrefix | 可以用于定位處理任務(wù)所在的線程池 | taskExecutor- |
線程池對任務(wù)的Reject策略RejectedExecutionHandler | 當(dāng)線程池運(yùn)行飽和,或者線程池處于shutdown臨界狀態(tài)時(shí),用來拒絕一個(gè)任務(wù)的執(zhí)行 | CallerRunsPolicy |
Reject策略預(yù)定義有四種:
- AbortPolicy,用于被拒絕任務(wù)的處理程序,它將拋出RejectedExecutionException。
- CallerRunsPolicy,用于被拒絕任務(wù)的處理程序,它直接在execute方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù)。
- DiscardOldestPolicy,用于被拒絕任務(wù)的處理程序,它放棄最舊的未處理請求,然后重試execute。
- DiscardPolicy,用于被拒絕任務(wù)的處理程序,默認(rèn)情況下它將丟棄被拒絕的任務(wù)。
創(chuàng)建 AsyncExecutorTask類,三個(gè)任務(wù)的配置和 AsyncTask 一樣,不同的是 @Async 注解需要指定前面配置的 線程池的名稱 taskExecutor。
@Component public class AsyncExecutorTask extends AbstractTask { ? ? @Async("taskExecutor") ? ? public Future<String> doTaskOneCallback() throws Exception { ? ? ? ? super.doTaskOne(); ? ? ? ? System.out.println("任務(wù)一,當(dāng)前線程:" + Thread.currentThread().getName()); ? ? ? ? return new AsyncResult<>("任務(wù)一完成"); ? ? } ? ? @Async("taskExecutor") ? ? public Future<String> doTaskTwoCallback() throws Exception { ? ? ? ? super.doTaskTwo(); ? ? ? ? System.out.println("任務(wù)二,當(dāng)前線程:" + Thread.currentThread().getName()); ? ? ? ? return new AsyncResult<>("任務(wù)二完成"); ? ? } ? ? @Async("taskExecutor") ? ? public Future<String> doTaskThreeCallback() throws Exception { ? ? ? ? super.doTaskThree(); ? ? ? ? System.out.println("任務(wù)三,當(dāng)前線程:" + Thread.currentThread().getName()); ? ? ? ? return new AsyncResult<>("任務(wù)三完成"); ? ? } }
在 單元測試 用例中,注入 AsyncExecutorTask 對象,并在測試用例中執(zhí)行 doTaskOne(),doTaskTwo(),doTaskThree() 三個(gè)方法。
@SpringBootTest public class AsyncExecutorTaskTest { ? ? @Autowired ? ? private AsyncExecutorTask task; ? ? @Test ? ? public void testAsyncExecutorTask() throws Exception { ? ? ? ? task.doTaskOneCallback(); ? ? ? ? task.doTaskTwoCallback(); ? ? ? ? task.doTaskThreeCallback(); ? ? ? ? sleep(30 * 1000L); ? ? } }
執(zhí)行一下上述的 單元測試,可以看到如下結(jié)果:
開始做任務(wù)一
開始做任務(wù)三
開始做任務(wù)二
完成任務(wù)二,耗時(shí):3905毫秒
任務(wù)二,當(dāng)前線程:taskExecutor-2
完成任務(wù)一,耗時(shí):6184毫秒
任務(wù)一,當(dāng)前線程:taskExecutor-1
完成任務(wù)三,耗時(shí):9737毫秒
任務(wù)三,當(dāng)前線程:taskExecutor-3
執(zhí)行上面的單元測試,觀察到 任務(wù)線程池 的 線程池名的前綴 被打印,說明 線程池 成功執(zhí)行 異步任務(wù)!
三、優(yōu)雅地關(guān)閉線程池
由于在應(yīng)用關(guān)閉的時(shí)候異步任務(wù)還在執(zhí)行,導(dǎo)致類似 數(shù)據(jù)庫連接池 這樣的對象一并被 銷毀了,當(dāng) 異步任務(wù) 中對 數(shù)據(jù)庫 進(jìn)行操作就會(huì)出錯(cuò)。
解決方案如下,重新設(shè)置線程池配置對象,新增線程池 setWaitForTasksToCompleteOnShutdown() 和 setAwaitTerminationSeconds() 配置:
@Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(20); executor.setThreadNamePrefix("taskExecutor-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor; }
- setWaitForTasksToCompleteOnShutdown(true): 該方法用來設(shè)置 線程池關(guān)閉 的時(shí)候 等待 所有任務(wù)都完成后,再繼續(xù) 銷毀 其他的 Bean,這樣這些 異步任務(wù) 的 銷毀 就會(huì)先于 數(shù)據(jù)庫連接池對象 的銷毀。
- setAwaitTerminationSeconds(60): 該方法用來設(shè)置線程池中 任務(wù)的等待時(shí)間,如果超過這個(gè)時(shí)間還沒有銷毀就 強(qiáng)制銷毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住。
異步任務(wù)** 的 銷毀 就會(huì)先于 數(shù)據(jù)庫連接池對象 的銷毀。
- setAwaitTerminationSeconds(60): 該方法用來設(shè)置線程池中 任務(wù)的等待時(shí)間,如果超過這個(gè)時(shí)間還沒有銷毀就 強(qiáng)制銷毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住。
到此這篇關(guān)于springboot為異步任務(wù)規(guī)劃自定義線程池的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)springboot異步自定義線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot應(yīng)用中線程池配置詳細(xì)教程(最新2021版)
- springboot使用線程池(ThreadPoolTaskExecutor)示例
- SpringBoot?項(xiàng)目中創(chuàng)建線程池
- Springboot線程池并發(fā)處理數(shù)據(jù)優(yōu)化方式
- SpringBoot+slf4j線程池全鏈路調(diào)用日志跟蹤問題及解決思路(二)
- SpringBoot異步使用@Async的原理以及線程池配置詳解
- Springboot 如何使用@Async整合線程池
- SpringBoot2線程池定義使用方法解析
- springboot線程池監(jiān)控的簡單實(shí)現(xiàn)
- SpringBoot實(shí)現(xiàn)線程池
- Springboot自帶線程池的實(shí)現(xiàn)
相關(guān)文章
IDEA啟動(dòng)Tomcat時(shí)控制臺出現(xiàn)亂碼問題及解決
這篇文章主要介紹了IDEA啟動(dòng)Tomcat時(shí)控制臺出現(xiàn)亂碼問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02Java字符串駝峰與下?lián)Q線格式轉(zhuǎn)換如何實(shí)現(xiàn)
這篇文章主要介紹了Java字符串駝峰與下?lián)Q線格式轉(zhuǎn)換如何實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11java 方法重寫與權(quán)限修飾符以及多態(tài)和抽象類詳解概念和用法
重寫是子類對父類的允許訪問的方法的實(shí)現(xiàn)過程進(jìn)行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫,權(quán)限修飾符用于控制被修飾變量、方法、類的可見范圍,說明了面向?qū)ο蟮姆庋b性,所以我們要適用他們盡可能的讓權(quán)限降到最低,從而安全性提高2021-10-10Mybatis-Plus支持GBase8s分頁查詢的實(shí)現(xiàn)示例
本文主要介紹了使?Mybatis-Plus?支持?GBase8s?的分頁查詢,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01SpringCloud微服務(wù)調(diào)用丟失請求頭的問題及解決方案
在Spring Cloud 中微服務(wù)之間的調(diào)用會(huì)用到Feign,但是在默認(rèn)情況下,Feign 調(diào)用遠(yuǎn)程服務(wù)存在Header請求頭丟失問題,下面給大家分享SpringCloud微服務(wù)調(diào)用丟失請求頭的問題及解決方案,感興趣的朋友一起看看吧2024-02-02Java開發(fā)工具-scala處理json格式利器-json4s詳解
這篇文章主要介紹了開發(fā)工具-scala處理json格式利器-json4s,文章中處理方法講解的很清楚,有需要的同學(xué)可以研究下2021-02-02Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例詳解
這篇文章主要介紹了Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例,簡單描述了裝飾者模式的概念、原理及Java使用裝飾者模式的相關(guān)實(shí)現(xiàn)步驟、操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-05-05