雖然線程池能大大提高服務(wù)器的并發(fā)性能,但使用它也會(huì)存在一定風(fēng)險(xiǎn)。與所有多線程應(yīng)用程序一樣,用線程池構(gòu)建的應(yīng)用程序容易產(chǎn)生各種并發(fā)問(wèn)題,如對(duì)共享資源的競(jìng)爭(zhēng)和死鎖。此外,如果線程池本身的實(shí)現(xiàn)不健壯,或者沒(méi)有合理地使用線程池,還容易導(dǎo)致與線程池有關(guān)的死鎖、系統(tǒng)資源不足和線程泄漏等問(wèn)題。
1.死鎖
任何多線程應(yīng)用程序都有死鎖風(fēng)險(xiǎn)。造成死鎖的最簡(jiǎn)單的情形是,線程A持有對(duì)象X的鎖,并且在等待對(duì)象Y的鎖,而線程B持有對(duì)象Y的鎖,并且在等待對(duì)象X的鎖。線程A與線程B都不釋放自己持有的鎖,并且等待對(duì)方的鎖,這就導(dǎo)致兩個(gè)線程永遠(yuǎn)等待下去,死鎖就這樣產(chǎn)生了。
雖然任何多線程程序都有死鎖的風(fēng)險(xiǎn),但線程池還會(huì)導(dǎo)致另外一種死鎖。在這種情形下,假定線程池中的所有工作線程都在執(zhí)行各自任務(wù)時(shí)被阻塞,它們都在等待某個(gè)任務(wù)A的執(zhí)行結(jié)果。而任務(wù)A依然在工作隊(duì)列中,由于沒(méi)有空閑線程,使得任務(wù)A一直不能被執(zhí)行。這使得線程池中的所有工作線程都永遠(yuǎn)阻塞下去,死鎖就這樣產(chǎn)生了。
2.系統(tǒng)資源不足
如果線程池中的線程數(shù)目非常多,這些線程會(huì)消耗包括內(nèi)存和其他系統(tǒng)資源在內(nèi)的大量資源,從而嚴(yán)重影響系統(tǒng)性能。
3.并發(fā)錯(cuò)誤
線程池的工作隊(duì)列依靠wait()和notify()方法來(lái)使工作線程及時(shí)取得任務(wù),但這兩個(gè)方法都難于使用。如果編碼不正確,可能會(huì)丟失通知,導(dǎo)致工作線程一直保持空閑狀態(tài),無(wú)視工作隊(duì)列中需要處理的任務(wù)。因此使用這些方法時(shí),必須格外小心,即便是專家也可能在這方面出錯(cuò)。最好使用現(xiàn)有的、比較成熟的線程池。例如,直接使用java.util.concurrent包中的線程池類。
4.線程泄漏
使用線程池的一個(gè)嚴(yán)重風(fēng)險(xiǎn)是線程泄漏。對(duì)于工作線程數(shù)目固定的線程池,如果工作線程在執(zhí)行任務(wù)時(shí)拋出 RuntimeException 或Error,并且這些異常或錯(cuò)誤沒(méi)有被捕獲,那么這個(gè)工作線程就會(huì)異常終止,使得線程池永久失去了一個(gè)工作線程。如果所有的工作線程都異常終止,線程池就最終變?yōu)榭眨瑳](méi)有任何可用的工作線程來(lái)處理任務(wù)。
導(dǎo)致線程泄漏的另一種情形是,工作線程在執(zhí)行一個(gè)任務(wù)時(shí)被阻塞,如等待用戶的輸入數(shù)據(jù),但是由于用戶一直不輸入數(shù)據(jù)(可能是因?yàn)橛脩糇唛_(kāi)了),導(dǎo)致這個(gè)工作線程一直被阻塞。這樣的工作線程名存實(shí)亡,它實(shí)際上不執(zhí)行任何任務(wù)了。假如線程池中所有的工作線程都處于這樣的阻塞狀態(tài),那么線程池就無(wú)法處理新加入的任務(wù)了。[nextpage]
5.任務(wù)過(guò)載
當(dāng)工作隊(duì)列中有大量排隊(duì)等候執(zhí)行的任務(wù)時(shí),這些任務(wù)本身可能會(huì)消耗太多的系統(tǒng)資源而引起系統(tǒng)資源缺乏。
綜上所述,線程池可能會(huì)帶來(lái)種種風(fēng)險(xiǎn),為了盡可能避免它們,使用線程池時(shí)需要遵循以下原則。
(1)如果任務(wù)A在執(zhí)行過(guò)程中需要同步等待任務(wù)B的執(zhí)行結(jié)果,那么任務(wù)A不適合加入到線程池的工作隊(duì)列中。如果把像任務(wù)A一樣的需要等待其他任務(wù)執(zhí)行結(jié)果的任務(wù)加入到工作隊(duì)列中,可能會(huì)導(dǎo)致線程池的死鎖。
(2)如果執(zhí)行某個(gè)任務(wù)時(shí)可能會(huì)阻塞,并且是長(zhǎng)時(shí)間的阻塞,則應(yīng)該設(shè)定超時(shí)時(shí)間,避免工作線程永久的阻塞下去而導(dǎo)致線程泄漏。在服務(wù)器程序中,當(dāng)線程等待客戶連接,或者等待客戶發(fā)送的數(shù)據(jù)時(shí),都可能會(huì)阻塞??梢酝ㄟ^(guò)以下方式設(shè)定超時(shí)時(shí)間:
調(diào)用ServerSocket的setSoTimeout(int timeout)方法,設(shè)定等待客戶連接的超時(shí)時(shí)間;
對(duì)于每個(gè)與客戶連接的Socket,調(diào)用該Socket的setSoTimeout(int timeout)方法,設(shè)定等待客戶發(fā)送數(shù)據(jù)的超時(shí)時(shí)間。
(3)了解任務(wù)的特點(diǎn),分析任務(wù)是執(zhí)行經(jīng)常會(huì)阻塞的I/O操作,還是執(zhí)行一直不會(huì)阻塞的運(yùn)算操作。前者時(shí)斷時(shí)續(xù)地占用CPU,而后者對(duì)CPU具有更高的利用率。預(yù)計(jì)完成任務(wù)大概需要多長(zhǎng)時(shí)間?是短時(shí)間任務(wù)還是長(zhǎng)時(shí)間任務(wù)?
根據(jù)任務(wù)的特點(diǎn),對(duì)任務(wù)進(jìn)行分類,然后把不同類型的任務(wù)分別加入到不同線程池的工作隊(duì)列中,這樣可以根據(jù)任務(wù)的特點(diǎn),分別調(diào)整每個(gè)線程池。
(4)調(diào)整線程池的大小。線程池的最佳大小主要取決于系統(tǒng)的可用CPU的數(shù)目,以及工作隊(duì)列中任務(wù)的特點(diǎn)。假如在一個(gè)具有 N 個(gè)CPU的系統(tǒng)上只有一個(gè)工作隊(duì)列,并且其中全部是運(yùn)算性質(zhì)(不會(huì)阻塞)的任務(wù),那么當(dāng)線程池具有 N 或 N+1 個(gè)工作線程時(shí),一般會(huì)獲得最大的 CPU 利用率。
如果工作隊(duì)列中包含會(huì)執(zhí)行I/O操作并常常阻塞的任務(wù),則要讓線程池的大小超過(guò)可用CPU的數(shù)目,因?yàn)椴⒉皇撬泄ぷ骶€程都一直在工作。選擇一個(gè)典型的任務(wù),然后估計(jì)在執(zhí)行這個(gè)任務(wù)的過(guò)程中,等待時(shí)間(WT)與實(shí)際占用CPU進(jìn)行運(yùn)算的時(shí)間(ST)之間的比例WT/ST。對(duì)于一個(gè)具有N個(gè)CPU的系統(tǒng),需要設(shè)置大約N×(1+WT/ST)個(gè)線程來(lái)保證CPU得到充分利用。
當(dāng)然,CPU利用率不是調(diào)整線程池大小過(guò)程中唯一要考慮的事項(xiàng)。隨著線程池中工作線程數(shù)目的增長(zhǎng),還會(huì)碰到內(nèi)存或者其他系統(tǒng)資源的限制,如套接字、打開(kāi)的文件句柄或數(shù)據(jù)庫(kù)連接數(shù)目等。要保證多線程消耗的系統(tǒng)資源在系統(tǒng)的承載范圍之內(nèi)。
(5)避免任務(wù)過(guò)載。服務(wù)器應(yīng)根據(jù)系統(tǒng)的承載能力,限制客戶并發(fā)連接的數(shù)目。當(dāng)客戶并發(fā)連接的數(shù)目超過(guò)了限制值,服務(wù)器可以拒絕連接請(qǐng)求,并友好地告知客戶:服務(wù)器正忙,請(qǐng)稍后再試。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。