為什么需要線程池
我們有兩種常見(jià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)存涛酗。那么有沒(méi)有一種方法能讓線程運(yùn)行完后不立即銷毀,而是讓線程重復(fù)使用偷厦,繼續(xù)執(zhí)行其他的任務(wù)哪商叹?
這就是線程池的由來(lái),很好的解決線程的重復(fù)利用只泼,避免重復(fù)開(kāi)銷剖笙。
線程池的優(yōu)點(diǎn)
1、線程是稀缺資源辜妓,使用線程池可以減少創(chuàng)建和銷毀線程的次數(shù)枯途,每個(gè)工作線程都可以重復(fù)使用忌怎。
2、可以根據(jù)系統(tǒng)的承受能力酪夷,調(diào)整線程池中工作線程的數(shù)量榴啸,防止因?yàn)橄倪^(guò)多內(nèi)存導(dǎo)致服務(wù)器崩潰。
線程池的風(fēng)險(xiǎn)
雖然線程池是構(gòu)建多線程應(yīng)用程序的強(qiáng)大機(jī)制晚岭,但使用它并不是沒(méi)有風(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í),我們就說(shuō)這組進(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)討論過(guò))而言锰什,它們通常執(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)度開(kāi)銷很小释牺,但如果有很多線程萝衩,環(huán)境切換也可能嚴(yán)重地影響程序的性能。
如果線程池太大没咙,那么被那些線程消耗的資源可能嚴(yán)重地影響系統(tǒng)性能猩谊。在線程之間進(jìn)行切換將會(huì)浪費(fèi)時(shí)間,而且使用超出比您實(shí)際需要的線程可能會(huì)引起資源匱乏問(wèn)題祭刚,因?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ù)完成后該線程卻沒(méi)有返回池時(shí),會(huì)發(fā)生這種情況扔亥。發(fā)生線程泄漏的一種情形出現(xiàn)在任務(wù)拋出一個(gè) RuntimeException 或一個(gè) Error 時(shí)场躯。
如果池類沒(méi)有捕捉到它們,那么線程只會(huì)退出而線程池的大小將會(huì)永久減少一個(gè)旅挤。當(dāng)這種情況發(fā)生的次數(shù)足夠多時(shí)踢关,線程池最終就為空,而且系統(tǒng)將停止粘茄,因?yàn)闆](méi)有可用的線程來(lái)處理任務(wù)签舞。
5.請(qǐng)求過(guò)載
僅僅是請(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è)置為T(mén)ERMINATED狀態(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ù)斋枢,如果不是(核心線程空閑或者還有核心線程沒(méi)有被創(chuàng)建)則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)帘靡。如果核心線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程瓤帚。
2描姚、線程池判斷工作隊(duì)列是否已滿,如果工作隊(duì)列沒(méi)有滿缘滥,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里轰胁。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程朝扼。
3、判斷線程池里的線程是否都處于工作狀態(tài)霎肯,如果沒(méi)有擎颖,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)榛斯。如果已經(jīng)滿了,則交給飽和策略來(lái)處理這個(gè)任務(wù)搂捧。
配置線程池大小配置
一般需要根據(jù)任務(wù)的類型來(lái)配置線程池大型运住:
如果是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)度超過(guò)處理需要况木,可靈活回收空閑線程垒拢,若無(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í)行。
以上就是線程池的詳細(xì)介紹俗他,后續(xù)將詳細(xì)講解四種線程池的具體實(shí)現(xiàn)脖捻。