深入了解Java線程池的原理和監(jiān)控
一、什么是線程池
簡單看名字就知道是裝有線程的池子,我們可以把要執(zhí)行的多線程交給線程池來處理,和連接池的概念一樣,通過維護(hù)一定數(shù)量的線程池來達(dá)到多個(gè)線程的復(fù)用。
二、線程池的好處
我們知道不用線程池的話,每個(gè)線程都要通過new Thread(xxRunnable).start()的方式來創(chuàng)建并運(yùn)行一個(gè)線程,線程少的話這不會(huì)是問題
而真實(shí)環(huán)境可能會(huì)開啟多個(gè)線程讓系統(tǒng)和程序達(dá)到最佳效率,當(dāng)線程數(shù)達(dá)到一定數(shù)量就會(huì)耗盡系統(tǒng)的CPU和內(nèi)存資源,也會(huì)造成GC頻繁收集和停頓
因?yàn)槊看蝿?chuàng)建和銷毀一個(gè)線程都是要消耗系統(tǒng)資源的,如果為每個(gè)任務(wù)都創(chuàng)建線程這無疑是一個(gè)很大的性能瓶頸。
所以,線程池中的線程復(fù)用極大節(jié)省了系統(tǒng)資源,當(dāng)線程一段時(shí)間不再有任務(wù)處理時(shí)它也會(huì)自動(dòng)銷毀,而不會(huì)長駐內(nèi)存。
三、使用線程池能解決的問題
1、效率
創(chuàng)建/銷毀線程伴隨著系統(tǒng)開銷,過于頻繁的創(chuàng)建/銷毀線程,會(huì)很大程度上影響處理效率
例如:記創(chuàng)建線程消耗時(shí)間T1,執(zhí)行任務(wù)消耗時(shí)間T2,銷毀線程消耗時(shí)間T3,如果T1+T3>T2,那么是不是說開啟一個(gè)線程來執(zhí)行這個(gè)任務(wù)太不劃算了!
正好,線程池緩存線程,可用已有的閑置線程來執(zhí)行新任務(wù),避免了T1+T3帶來的系統(tǒng)開銷
2、阻塞
線程并發(fā)數(shù)量過多,搶占系統(tǒng)資源從而導(dǎo)致阻塞
我們知道線程能共享系統(tǒng)資源,如果同時(shí)執(zhí)行的線程過多,就有可能導(dǎo)致系統(tǒng)資源不足而產(chǎn)生阻塞的情況 運(yùn)用線程池能有效的控制線程最大并發(fā)數(shù),避免以上的問題
3、對(duì)線程進(jìn)行一些簡單的管理
比如:延時(shí)執(zhí)行、定時(shí)循環(huán)執(zhí)行的策略等,運(yùn)用線程池都能進(jìn)行很好的實(shí)現(xiàn)
四、線程池的優(yōu)勢
在業(yè)務(wù)場景中, 如果一個(gè)對(duì)象創(chuàng)建銷毀開銷比較大, 那么此時(shí)建議池化對(duì)象進(jìn)行管理。
例如線程,jdbc連接等等, 在高并發(fā)場景中, 如果可以復(fù)用之前銷毀的對(duì)象, 那么系統(tǒng)效率將大大提升。
另外一個(gè)好處是可以設(shè)定池化對(duì)象的上限, 例如預(yù)防創(chuàng)建線程數(shù)量過多導(dǎo)致系統(tǒng)崩潰的場景。
五、線程池類的主要參數(shù)
- corePoolSize:線程池的核心大小,也可以理解為最小的線程池大小。
- maximumPoolSize:最大線程池大小。
- keepAliveTime:空余線程存活時(shí)間,指的是超過corePoolSize的空余線程達(dá)到多長時(shí)間才進(jìn)行銷毀。
- unit:銷毀時(shí)間單位。
- workQueue:存儲(chǔ)等待執(zhí)行線程的工作隊(duì)列。
- threadFactory:創(chuàng)建線程的工廠,一般用默認(rèn)即可。
- handler:拒絕策略,當(dāng)工作隊(duì)列、線程池全已滿時(shí)如何拒絕新任務(wù),默認(rèn)拋出異常。
六、線程池的工作原理
如果線程池中的線程小于corePoolSize時(shí)就會(huì)創(chuàng)建新線程直接執(zhí)行任務(wù)。
如果線程池中的線程大于corePoolSize時(shí)就會(huì)暫時(shí)把任務(wù)存儲(chǔ)到工作隊(duì)列workQueue中等待執(zhí)行。
如果工作隊(duì)列workQueue也滿時(shí),當(dāng)線程數(shù)小于最大線程池?cái)?shù)maximumPoolSize時(shí)就會(huì)創(chuàng)建新線程來處理,而線程數(shù)大于等于最大線程池?cái)?shù)maximumPoolSize時(shí)就會(huì)執(zhí)行拒絕策略。
一般流程圖如下
七、線程池大小設(shè)置
配置線程池的大小可根據(jù)以下幾個(gè)維度進(jìn)行分析來配置合理的線程數(shù):
- 任務(wù)性質(zhì)可分為:CPU密集型任務(wù),IO密集型任務(wù),混合型任務(wù)。
- 任務(wù)的執(zhí)行時(shí)長。
- 任務(wù)是否有依賴——依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接等。
1、CPU密集型任務(wù)
盡量使用較小的線程池,一般為CPU核數(shù)+1。 因?yàn)镃PU密集型任務(wù)使得CPU使用率很高,若開過多的線程數(shù),只能增加上下文切換的次數(shù),因此會(huì)帶來額外的開銷。
2、IO密集型任務(wù)
可以使用稍大的線程池,一般為2*CPU核數(shù)+1。 因?yàn)镮O操作不占用CPU,不要讓CPU閑下來,應(yīng)加大線程數(shù)量,因此可以讓CPU在等待IO的時(shí)候去處理別的任務(wù),充分利用CPU時(shí)間。
3、混合型任務(wù)
可以將任務(wù)分成IO密集型和CPU密集型任務(wù),然后分別用不同的線程池去處理。 只要分完之后兩個(gè)任務(wù)的執(zhí)行時(shí)間相差不大,那么就會(huì)比串行執(zhí)行來的高效。 因?yàn)槿绻麆澐种髢蓚€(gè)任務(wù)執(zhí)行時(shí)間相差甚遠(yuǎn),那么先執(zhí)行完的任務(wù)就要等后執(zhí)行完的任務(wù),最終的時(shí)間仍然取決于后執(zhí)行完的任務(wù),而且還要加上任務(wù)拆分與合并的開銷,得不償失
4、依賴其他資源
如某個(gè)任務(wù)依賴數(shù)據(jù)庫的連接返回的結(jié)果,這時(shí)候等待的時(shí)間越長,則CPU空閑的時(shí)間越長,那么線程數(shù)量應(yīng)設(shè)置得越大,才能更好的利用CPU。 借鑒別人的文章 對(duì)線程池大小的估算公式:
最佳線程數(shù)目 = ((線程等待時(shí)間+線程CPU時(shí)間)/線程CPU時(shí)間 )* CPU數(shù)目
比如平均每個(gè)線程CPU運(yùn)行時(shí)間為0.5s,而線程等待時(shí)間(非CPU運(yùn)行時(shí)間,比如IO)為1.5s,CPU核心數(shù)為8,那么根據(jù)上面這個(gè)公式估算得到:((0.5+1.5)/0.5)*8=32。
可以得出一個(gè)結(jié)論: 線程等待時(shí)間所占比例越高,需要越多線程。線程CPU時(shí)間所占比例越高,需要越少線程。
但是通過公式計(jì)算出來的線程池大小也只是參考,準(zhǔn)確的線程池設(shè)置大小還是需要經(jīng)過性能測試驗(yàn)證獲得的。
八、線程池監(jiān)控
通過工具類監(jiān)控線程池的使用、狀態(tài)等,例如通過JDK自帶的Jconsole、Jvisualvm監(jiān)控
1)首先我們先手動(dòng)創(chuàng)建一個(gè)簡單的死鎖程序并運(yùn)行
2)使用JDK自帶的Jconsole進(jìn)行監(jiān)控,選擇text進(jìn)程并連接
3)點(diǎn)擊檢測死鎖,選擇線程并查看信息,可以定位到j(luò)ava代碼發(fā)生死鎖的位置
4)使用JAVA自帶的jvisualvm進(jìn)行監(jiān)控,選擇text進(jìn)程
5)選擇線程TAB,查看線程信息,如果存在死鎖會(huì)出現(xiàn)下圖的樣式
6)點(diǎn)擊線程Dump之后進(jìn)入新頁面拖到最底下就能看到線程死鎖詳細(xì)信息
另外除了監(jiān)控線程死鎖,還可以通過線程池提供的以下參數(shù)對(duì)線程池進(jìn)行監(jiān)控。
- taskCount:線程池需要執(zhí)行的任務(wù)數(shù)量,包括已經(jīng)執(zhí)行完的、未執(zhí)行的和正在執(zhí)行的。
- completedTaskCount:線程池在運(yùn)行過程中已完成的任務(wù)數(shù)量,completedTaskCount <= taskCount。
- largestPoolSize:線程池曾經(jīng)創(chuàng)建過的最大線程數(shù)量,通過這個(gè)數(shù)據(jù)可以知道線程池是否滿過。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。
- getPoolSize: 線程池的線程數(shù)量。如果線程池不銷毀的話,池里的線程不會(huì)自動(dòng)銷毀,所以線程池的線程數(shù)量只增不減。
- getActiveCount:獲取活動(dòng)的線程數(shù)。
九、總結(jié)
對(duì)于監(jiān)控而言,不在于手段的多樣性,而需要明白監(jiān)控的本質(zhì),以及需要的監(jiān)控項(xiàng)內(nèi)容,找出系統(tǒng)瓶頸,規(guī)避風(fēng)險(xiǎn)。
到此這篇關(guān)于深入了解Java線程池的原理和監(jiān)控的文章就介紹到這了,更多相關(guān)Java線程池原理和監(jiān)控內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器
這篇文章主要介紹了使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12無感NullPointerException的值相等判斷方法
當(dāng)我們需要去判斷一個(gè)?入?yún)?查庫?返回的開關(guān)變量(通常是個(gè)Integer類型的)時(shí),常常會(huì)寫如下的if-else判斷語句。但又會(huì)為在生產(chǎn)環(huán)境看到的「NullPointerException」感到困擾,遇到這個(gè)問題如何處理呢,下面小編通過本文給大家詳細(xì)講解,需要的朋友參考下吧2023-02-02Java實(shí)現(xiàn)將PDF轉(zhuǎn)為PDF/A
通過將PDF格式轉(zhuǎn)換為PDF/A格式,可保護(hù)文檔布局、格式、字體、大小等不受更改,從而實(shí)現(xiàn)文檔安全保護(hù)的目的,同時(shí)又能保證文檔可讀、可訪問。本文將為大家介紹如何實(shí)現(xiàn)這一轉(zhuǎn)換,需要的可以參考一下2022-01-01java處理轉(zhuǎn)義字符↑ → ↓ 保存后的展示還原操作
這篇文章主要介紹了java處理轉(zhuǎn)義字符↑ → ↓ 保存后的展示還原操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Spring Security學(xué)習(xí)之rememberMe自動(dòng)登錄的實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于Spring Security學(xué)習(xí)之rememberMe自動(dòng)登錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06SpringBoot集成JWT生成token及校驗(yàn)方法過程解析
這篇文章主要介紹了SpringBoot集成JWT生成token及校驗(yàn)方法過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java線程的創(chuàng)建介紹及實(shí)現(xiàn)方式示例
這篇文章主要為大家介紹了Java線程的創(chuàng)建介紹及實(shí)現(xiàn)方式示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09