java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)
為什么需要線程池
我們有兩種常見的創(chuàng)建線程的方法,一種是繼承Thread類,一種是實(shí)現(xiàn)Runnable的接口,Thread類其實(shí)也是實(shí)現(xiàn)了Runnable接口。但是我們創(chuàng)建這兩種線程在運(yùn)行結(jié)束后都會(huì)被虛擬機(jī)銷毀,如果線程數(shù)量多的話,頻繁的創(chuàng)建和銷毀線程會(huì)大大浪費(fèi)時(shí)間和效率,更重要的是浪費(fèi)內(nèi)存。那么有沒有一種方法能讓線程運(yùn)行完后不立即銷毀,而是讓線程重復(fù)使用,繼續(xù)執(zhí)行其他的任務(wù)哪?
這就是線程池的由來(lái),很好的解決線程的重復(fù)利用,避免重復(fù)開銷。
線程池的優(yōu)點(diǎn)
1、線程是稀缺資源,使用線程池可以減少創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以重復(fù)使用。
2、可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線程的數(shù)量,防止因?yàn)橄倪^多內(nèi)存導(dǎo)致服務(wù)器崩潰。
線程池的風(fēng)險(xiǎn)
雖然線程池是構(gòu)建多線程應(yīng)用程序的強(qiáng)大機(jī)制,但使用它并不是沒有風(fēng)險(xiǎn)的。用線程池構(gòu)建的應(yīng)用程序容易遭受任何其它多線程應(yīng)用程序容易遭受的所有并發(fā)風(fēng)險(xiǎn),諸如同步錯(cuò)誤和死鎖,它還容易遭受特定于線程池的少數(shù)其它風(fēng)險(xiǎn),諸如與池有關(guān)的死鎖、資源不足和線程泄漏。
1.死鎖
任何多線程應(yīng)用程序都有死鎖風(fēng)險(xiǎn)。當(dāng)一組進(jìn)程或線程中的每一個(gè)都在等待一個(gè)只有該組中另一個(gè)進(jìn)程才能引起的事件時(shí),我們就說這組進(jìn)程或線程 死鎖了。死鎖的最簡(jiǎn)單情形是:線程 A 持有對(duì)象 X 的獨(dú)占鎖,并且在等待對(duì)象 Y 的鎖,而線程 B 持有對(duì)象 Y 的獨(dú)占鎖,卻在等待對(duì)象 X 的鎖。除非有某種方法來(lái)打破對(duì)鎖的等待(Java 鎖定不支持這種方法),否則死鎖的線程將永遠(yuǎn)等下去。
2.資源不足
線程池的一個(gè)優(yōu)點(diǎn)在于:相對(duì)于其它替代調(diào)度機(jī)制(有些我們已經(jīng)討論過)而言,它們通常執(zhí)行得很好。但只有恰當(dāng)?shù)卣{(diào)整了線程池大小時(shí)才是這樣的。
線程消耗包括內(nèi)存和其它系統(tǒng)資源在內(nèi)的大量資源。除了
Thread 對(duì)象所需的內(nèi)存之外,每個(gè)線程都需要兩個(gè)可能很大的執(zhí)行調(diào)用堆棧。除此以外,JVM 可能會(huì)為每個(gè) Java
線程創(chuàng)建一個(gè)本機(jī)線程,這些本機(jī)線程將消耗額外的系統(tǒng)資源。最后,雖然線程之間切換的調(diào)度開銷很小,但如果有很多線程,環(huán)境切換也可能嚴(yán)重地影響程序的性能。
如果線程池太大,那么被那些線程消耗的資源可能嚴(yán)重地影響系統(tǒng)性能。在線程之間進(jìn)行切換將會(huì)浪費(fèi)時(shí)間,而且使用超出比您實(shí)際需要的線程可能會(huì)引起資源匱乏問題,因?yàn)槌鼐€程正在消耗一些資源,而這些資源可能會(huì)被其它任務(wù)更有效地利用。
除了線程自身所使用的資源以外,服務(wù)請(qǐng)求時(shí)所做的工作可能需要其它資源,例如 JDBC 連接、套接字或文件,這些也都是有限資源,有太多的并發(fā)請(qǐng)求也可能引起失效,例如不能分配 JDBC 連接。
3.并發(fā)錯(cuò)誤
線程池和其它排隊(duì)機(jī)制依靠使用
wait() 和 notify()
方法,這兩個(gè)方法都難于使用。如果編碼不正確,那么可能丟失通知,導(dǎo)致線程保持空閑狀態(tài),盡管隊(duì)列中有工作要處理。使用這些方法時(shí),必須格外小心;即便是專家也可能在它們上面出錯(cuò)。而最好使用現(xiàn)有的、已經(jīng)知道能工作的實(shí)現(xiàn),例如在
util.concurrent 包。
4.線程泄漏
各種類型的線程池中一個(gè)嚴(yán)重的風(fēng)險(xiǎn)是線程泄漏,當(dāng)從池中除去一個(gè)線程以執(zhí)行一項(xiàng)任務(wù),而在任務(wù)完成后該線程卻沒有返回池時(shí),會(huì)發(fā)生這種情況。發(fā)生線程泄漏的一種情形出現(xiàn)在任務(wù)拋出一個(gè) RuntimeException 或一個(gè) Error 時(shí)。
如果池類沒有捕捉到它們,那么線程只會(huì)退出而線程池的大小將會(huì)永久減少一個(gè)。當(dāng)這種情況發(fā)生的次數(shù)足夠多時(shí),線程池最終就為空,而且系統(tǒng)將停止,因?yàn)闆]有可用的線程來(lái)處理任務(wù)。
5.請(qǐng)求過載
僅僅是請(qǐng)求就壓垮了服務(wù)器,這種情況是可能的。在這種情形下,我們可能不想將每個(gè)到來(lái)的請(qǐng)求都排隊(duì)到我們的工作隊(duì)列,因?yàn)榕旁陉?duì)列中等待執(zhí)行的任務(wù)可能會(huì)消耗太多的系統(tǒng)資源并引起資源缺乏。在這種情形下決定如何做取決于您自己;在某些情況下,您可以簡(jiǎn)單地拋棄請(qǐng)求,依靠更高級(jí)別的協(xié)議稍后重試請(qǐng)求,您也可以用一個(gè)指出服務(wù)器暫時(shí)很忙的響應(yīng)來(lái)拒絕請(qǐng)求。
線程池的實(shí)現(xiàn)原理
線程池
1.線程池狀態(tài)
線程池和線程一樣擁有自己的狀態(tài),在ThreadPoolExecutor類中定義了一個(gè)volatile變量runState來(lái)表示線程池的狀態(tài),線程池有四種狀態(tài),分別為RUNNING、SHURDOWN、STOP、TERMINATED。
- 線程池創(chuàng)建后處于RUNNING狀態(tài)。
- 調(diào)用shutdown后處于SHUTDOWN狀態(tài),線程池不能接受新的任務(wù),會(huì)等待緩沖隊(duì)列的任務(wù)完成。
- 調(diào)用shutdownNow后處于STOP狀態(tài),線程池不能接受新的任務(wù),并嘗試終止正在執(zhí)行的任務(wù)。
- 當(dāng)線程池處于SHUTDOWN或STOP狀態(tài),并且所有工作線程已經(jīng)銷毀,任務(wù)緩存隊(duì)列已經(jīng)清空或執(zhí)行結(jié)束后,線程池被設(shè)置為TERMINATED狀態(tài)。
線程池原理:預(yù)先啟動(dòng)一些線程,線程無(wú)限循環(huán)從任務(wù)隊(duì)列中獲取一個(gè)任務(wù)進(jìn)行執(zhí)行,直到線程池被關(guān)閉。如果某個(gè)線程因?yàn)閳?zhí)行某個(gè)任務(wù)發(fā)生異常而終止,那么重新創(chuàng)建一個(gè)新的線程而已,如此反復(fù)。
2.線程池的處理流程
1、判斷線程池里的核心線程是否都在執(zhí)行任務(wù),如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程。
2、線程池判斷工作隊(duì)列是否已滿,如果工作隊(duì)列沒有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程。
3、判斷線程池里的線程是否都處于工作狀態(tài),如果沒有,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來(lái)處理這個(gè)任務(wù)。
配置線程池大小配置
一般需要根據(jù)任務(wù)的類型來(lái)配置線程池大?。?/p>
如果是CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)為 NCPU+1
如果是IO密集型任務(wù),參考值可以設(shè)置為2*NCPU
當(dāng)然,這只是一個(gè)參考值,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整,比如可以先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載、資源利用率來(lái)進(jìn)行適當(dāng)調(diào)整。
Java提供的四種線程池實(shí)現(xiàn)
(1)newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
(2)newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。
(3)newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
(4)newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。
以上就是java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于java 線程池的實(shí)現(xiàn)原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)戰(zhàn)寵物醫(yī)院預(yù)約掛號(hào)系統(tǒng)的實(shí)現(xiàn)流程
只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+JSP+Spring+SpringBoot+MyBatis+html+layui+maven+Mysql實(shí)現(xiàn)一個(gè)寵物醫(yī)院預(yù)約掛號(hào)系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2022-01-01java實(shí)現(xiàn)一個(gè)簡(jiǎn)單TCPSocket聊天室功能分享
這篇文章主要為大家分享了java實(shí)現(xiàn)的一個(gè)簡(jiǎn)單TCPSocket聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04Java之SpringCloud Eurka注冊(cè)錯(cuò)誤解決方案
這篇文章主要介紹了Java之SpringCloud Eurka注冊(cè)錯(cuò)誤解決方案,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07JAVA實(shí)現(xiàn)連接本地打印機(jī)并打印文件的實(shí)現(xiàn)代碼
這篇文章主要介紹了JAVA實(shí)現(xiàn)連接本地打印機(jī)并打印文件的實(shí)現(xiàn)代碼,需要的朋友可以參考下2019-10-10spring boot啟動(dòng)時(shí)加載外部配置文件的方法
這篇文章主要給大家介紹了關(guān)于spring boot啟動(dòng)時(shí)加載外部配置文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02