線程池的使用場(chǎng)景和工作原理
?近期工作中遇到線程池參數(shù)配置不當(dāng)引發(fā)的相關(guān)問(wèn)題?;诖?,本文主要結(jié)合線程池的原理、使用場(chǎng)景、參數(shù)配置、使用注意事項(xiàng)等詳細(xì)闡述。文章較長(zhǎng),預(yù)計(jì)閱讀時(shí)長(zhǎng)8~10分鐘,建議收藏。
圖片
前置知識(shí)
- 池化技術(shù)一種資源管理策略,旨在提高資源利用率和系統(tǒng)性能,減少資源創(chuàng)建和銷毀的開(kāi)銷。如數(shù)據(jù)庫(kù)連接池、線程池、常量池等。
- 線程和進(jìn)程進(jìn)程就像是你打開(kāi)的360安全衛(wèi)士這個(gè)軟件本身。線程:在這個(gè)空間里,360安全衛(wèi)士可以運(yùn)行它的各種功能,比如病毒掃描、垃圾清理等都是一個(gè)線程在處理。
- 多線程在計(jì)算機(jī)中,CPU(中央處理器)是執(zhí)行線程的地方。在多核CPU的系統(tǒng)中,每個(gè)核心可以獨(dú)立執(zhí)行線程。即多個(gè)線程可以真正地同時(shí)運(yùn)行。
簡(jiǎn)單了解幾個(gè)關(guān)鍵詞,我們說(shuō)說(shuō)什么是線程池?
1.什么是線程池?
線程池是一種線程管理和復(fù)用的機(jī)制。
其核心思想是:預(yù)先創(chuàng)建一定數(shù)量的線程,并把它們保存在線程池中,用的時(shí)候拿出來(lái),用完了丟進(jìn)去。而不是每次有任務(wù)過(guò)來(lái)重新創(chuàng)建新的線程。如下圖,其核心組成:
- 工作線程(Worker):這些是線程池中實(shí)際執(zhí)行任務(wù)的線程。
- 任務(wù)隊(duì)列(Task Queue):這是一個(gè)存放待執(zhí)行任務(wù)的隊(duì)列,工作線程會(huì)從這個(gè)隊(duì)列中取出任務(wù)來(lái)執(zhí)行。
- 線程管理器(Thread Manager):負(fù)責(zé)管理線程的生命周期,包括創(chuàng)建新線程、監(jiān)控線程狀態(tài)以及銷毀不再需要的線程。
- 線程工廠(Thread Factory):用于創(chuàng)建新線程的組件,可以定制線程的名稱、優(yōu)先級(jí)等屬性。
- 拒絕策略(Rejection Policy):當(dāng)任務(wù)隊(duì)列滿了,且工作線程都在忙碌時(shí),新提交的任務(wù)將被拒絕,拒絕策略定義了如何處理這些被拒絕的任務(wù)。
- 調(diào)度器(Scheduler):雖然不是所有線程池都有,但有些線程池會(huì)包含調(diào)度器,用于安排任務(wù)在特定時(shí)間執(zhí)行。
架構(gòu)圖
圖片
2.線程池有什么好處?
線程池的優(yōu)勢(shì)總結(jié)為以下幾點(diǎn):
- 減少資源消耗:
線程池通過(guò)預(yù)先創(chuàng)建線程并復(fù)用它們,減少了頻繁創(chuàng)建和銷毀線程所帶來(lái)的系統(tǒng)資源消耗。
- 提高系統(tǒng)性能:
通過(guò)合理配置線程池大小,可以最大限度地壓榨多核CPU的性能,從而提高機(jī)器的處理能力。
- 提高響應(yīng)速度:
線程池中的線程是預(yù)先創(chuàng)建的,這意味著當(dāng)新任務(wù)到達(dá)時(shí),可以立即被處理,而不需要等待線程的創(chuàng)建,這樣可以顯著提高任務(wù)的響應(yīng)速度。
- 增強(qiáng)并發(fā)性能:
線程池支持多線程并發(fā)執(zhí)行任務(wù),這樣可以同時(shí)處理多個(gè)任務(wù),增強(qiáng)系統(tǒng)的并發(fā)處理能力。
線程池有哪些使用場(chǎng)景?
線程池是一種多線程管理工具,它提供了線程的復(fù)用和調(diào)度功能,可以顯著提高程序的并發(fā)性能和資源利用率。以下是線程池的一些常見(jiàn)使用場(chǎng)景:
- 文件上傳下載
如:導(dǎo)入、導(dǎo)出功能、文件批數(shù)據(jù)處理等。
- 異步任務(wù)處理
如發(fā)送郵件、日志記錄等.
- 定時(shí)任務(wù)
如定時(shí)備份、定時(shí)清理緩存等,日文件、月文件數(shù)據(jù)同步等
- 批處理
如數(shù)據(jù)分析、統(tǒng)計(jì)報(bào)表生成等。
- 快速響應(yīng)用戶請(qǐng)求
如用戶查詢商品詳情頁(yè),可以考慮使用線程池并發(fā)地查詢價(jià)格、優(yōu)惠、庫(kù)存等信息,再聚合結(jié)果返回,降低接口總響應(yīng)時(shí)間。
線程池是如何工作的?
曾經(jīng)有面試官這樣問(wèn):核心線程數(shù)10,最大線程數(shù)20,阻塞隊(duì)列最大100,假如我有100個(gè)線程同時(shí)進(jìn)來(lái),線程處理任務(wù)時(shí)間平均按1s計(jì)算,那理想情況下多長(zhǎng)時(shí)間可以處理完?
基于上邊的問(wèn)題,線程池的工作原理可以概括為以下幾個(gè)步驟:
- 工作原理(簡(jiǎn)易版)
圖片
- 任務(wù)提交:
開(kāi)發(fā)人員使用 ThreadPoolExecutor 的 submit() 方法提交需要執(zhí)行的任務(wù)。這些任務(wù)通常是實(shí)現(xiàn)了 Callable 或 Runnable 接口的對(duì)象。
- 狀態(tài)檢查:
線程池會(huì)檢查自身的運(yùn)行狀態(tài)。如果線程池不是處于 RUNNING 狀態(tài),那么會(huì)直接拒絕新提交的任務(wù)。
- 任務(wù)封裝:
被提交的任務(wù)會(huì)被封裝成一個(gè) FutureTask 對(duì)象。FutureTask 實(shí)現(xiàn)了 Future 接口,用來(lái)獲取任務(wù)的執(zhí)行結(jié)果。
- 核心線程處理:
如果線程池的核心線程數(shù)小于 corePoolSize,線程池會(huì)嘗試創(chuàng)建一個(gè)新的核心線程來(lái)執(zhí)行這個(gè)任務(wù)。
- 任務(wù)隊(duì)列處理:
如果核心線程數(shù)已經(jīng)達(dá)到 corePoolSize,則任務(wù)會(huì)被放入一個(gè)任務(wù)隊(duì)列中,等待工作線程從隊(duì)列中取出并執(zhí)行。
- 非核心線程處理:
如果任務(wù)隊(duì)列已滿,并且當(dāng)前線程池中的線程數(shù)量小于 maximumPoolSize,則線程池會(huì)嘗試創(chuàng)建新的非核心線程來(lái)執(zhí)行任務(wù)。
- 拒絕策略處理:
如果線程池中的線程數(shù)量已經(jīng)達(dá)到 maximumPoolSize,并且任務(wù)隊(duì)列也已滿,線程池將根據(jù)設(shè)定的拒絕策略來(lái)處理新提交的任務(wù)。
- 任務(wù)執(zhí)行與結(jié)果獲?。?/li>
任務(wù)執(zhí)行完成后,線程池會(huì)返回一個(gè) Future 對(duì)象。通過(guò)這個(gè) Future 對(duì)象,可以查詢?nèi)蝿?wù)是否完成,以及獲取任務(wù)的執(zhí)行結(jié)果。
??總之,歸納起來(lái)的執(zhí)行流程:
圖片
線程池參數(shù)如何設(shè)置?
《阿里巴巴Java開(kāi)發(fā)手冊(cè)》中規(guī)約中強(qiáng)調(diào):
圖片
因此實(shí)際工作中,我們通常自定義線程池。其核心參數(shù):
public ThreadPoolExecutor(
int corePoolSize, // 核心線程數(shù),線程池中始終保持的線程數(shù),即使它們處于空閑狀態(tài)
int maximumPoolSize, // 最大線程數(shù),線程池中允許的最大線程數(shù)
long keepAliveTime, // 非核心線程空閑存活時(shí)間,當(dāng)線程池中正在運(yùn)行的線程數(shù)量超過(guò)了核心線程數(shù)時(shí),多余的線程在空閑時(shí)間達(dá)到這個(gè)值后會(huì)被終止
TimeUnit unit, // 存活時(shí)間單位,與keepAliveTime一起使用,表示keepAliveTime的時(shí)間單位
BlockingQueue<Runnable> workQueue, // 工作隊(duì)列,用于存放待執(zhí)行任務(wù)的阻塞隊(duì)列
ThreadFactory threadFactory, // 線程工廠,用于創(chuàng)建新線程
RejectedExecutionHandler handler // 拒絕策略,當(dāng)任務(wù)太多,無(wú)法被線程池及時(shí)處理時(shí),采取的策略
)
參數(shù)最佳實(shí)踐:
實(shí)際上,大都數(shù)公司的線程池配置依照自身業(yè)務(wù)場(chǎng)景和機(jī)器性能配置的。這里介紹的只是個(gè)參考,能說(shuō)清楚利弊就好
- corePoolSize, // 核心線程數(shù)
- CPU密集型可以將線程數(shù)設(shè)置為 N(CPU 核數(shù))+1,比 CPU 核心數(shù)多出來(lái)的一個(gè)線程是為了防止線程偶發(fā)的缺頁(yè)中斷。
- IO密集型系統(tǒng)會(huì)用大部分的時(shí)間來(lái)處理 I/O 交互,而線程在處理 I/O 的時(shí)間段內(nèi)不會(huì)占用 CPU 來(lái)處理,這時(shí)就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務(wù)的應(yīng)用中,我們可以多配置一些線程,具體的計(jì)算方法可以是 :核心線程數(shù)=CPU核數(shù)*2。
如何理解 CPU 密集 和I/O密集?
CPU密集型任務(wù)通常在CPU上執(zhí)行進(jìn)行大量計(jì)算(如RSA加密)I/O密集多指I/O操作(主要磁盤(pán)讀寫(xiě),如 數(shù)據(jù)庫(kù)操作等)
附加:如何獲取CPU核數(shù)?
代碼:
Runtime.getRuntime().availableProcessors();
命令:
$ lscpu
- maximumPoolSize // 最大線程數(shù)
具體依據(jù)服務(wù)器的I/O性能,經(jīng)驗(yàn)法則:
對(duì)于I/O密集型應(yīng)用,通常將 maximumPoolSize 設(shè)置為可用處理器核心數(shù)的5到10倍。
文件處理服務(wù):如果服務(wù)需要處理大量的文件上傳和下載,maximumPoolSize 可以設(shè)置為20或更多。
- keepAliveTime // 非核心線程空閑存活時(shí)間
看業(yè)務(wù)實(shí)時(shí)性高不高,一般系統(tǒng),設(shè)置60s亦可
- unit // 存活時(shí)間單位
看業(yè)務(wù)實(shí)時(shí)性高不高,一般系統(tǒng),設(shè)置s亦可
- workQueue // 工作隊(duì)列
按目前經(jīng)驗(yàn),工作中常用:
LinkedBlockingQueue 是一個(gè)無(wú)界隊(duì)列,適用于任務(wù)數(shù)量可能突然激增的場(chǎng)景;
ArrayBlockingQueue 是一個(gè)有界隊(duì)列,適用于需要限制最大任務(wù)數(shù)量的場(chǎng)景,以避免資源耗盡
- threadFactory // 線程工廠一般使用默認(rèn)的也可
- handler // 拒絕策略
主要有4種拒絕策略:
AbortPolicy:直接丟棄任務(wù),拋出異常,這是默認(rèn)策略
CallerRunsPolicy:只用調(diào)用者所在的線程來(lái)處理任務(wù)
DiscardOldestPolicy:丟棄等待隊(duì)列中最舊的任務(wù),并執(zhí)行當(dāng)前任務(wù)
DiscardPolicy:直接丟棄任務(wù),也不拋出異常
介于此,給出公司中用到的其中一個(gè)案例配置,僅供參考。
@Bean("XXXAsyncPool")
public Executor myTaskAsyncPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心線程數(shù)
executor.setCorePoolSize(10);
//配置核心線程數(shù)
executor.setMaxPoolSize(20);
//配置隊(duì)列容量
executor.setQueueCapacity(1000);
//設(shè)置線程活躍時(shí)間
executor.setKeepAliveSeconds(60);
//設(shè)置線程名
executor.setThreadNamePrefix("XXXAsyncPool-");
//設(shè)置拒絕策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
拒絕策略先前遇到坑,
篇幅,稍后單獨(dú)做個(gè)介紹。