朱 翔
( 中南大學資訊科學與工程學院,湖南 長沙 4 10075)
摘 要:在對象池擴充設計模式的基礎上 ,設計了高效線程池. 研究了線程池中的死結問
題、線程分配問題、線程泄漏問題 ,提出了相應的解決方案 , 實現了線程池的策略管理與
線程的高效複用 ,提高了系統效率.
關鍵詞:設計模式;對象池;線程池;線程死結;線程分配;線程泄漏
中圖分類號: TP311. 11 文獻標識碼:A
Research and Design Based on the Highly Efficient Thread Pool
ZHU Xiang
(College of Information Science and Engineering ,Central South University ,Changsha 4 10075 ,China)
Abstract :On the basis of object pool expansion ’s designed pattern , the highly efficient thread pool
is designed ;studies are made on the thread deadlock , thread distribution , thread leak ;and the
relevant resolutions are also proposed ,which put s the strategy management of thread pool and
reuse of thread into practice , thus enhances the efficiency of the whole system.
Keywords :design pattern ;object pool ;thread pool ;thread deadlock ;thread distribution ;thread leak
在大型軟體設計中,存在一些寶貴資源 ,如記憶體、線程、資料庫連接等 ,但這些資源的使用
在時間或空間上極大地影響軟體的使用效率 ,使資源的可用性與用戶端應用程式的效能和用
戶的滿意程度密切相關. 因此 ,資源管理是開發可伸縮、高並向度 c/ s 結構的應用程式時需考
慮的一個重要問題. 為了將這些寶貴資源充分利用 ,在構造可為多並發使用者提供服務的用戶端
應用程式時,資源管理的指導原則是儘可能遲地分配資源 ,並儘可能早地解除資源分派. 因此
軟體設計人員採用了共用的方式 ,將這些資源在時間或空間上盡量複用.
在大型並發應用中,線程是一個費時費資源的工作 ,因為每個線程都需要2 個可能很大的
執行呼叫堆疊;雖然線程之間切換的調度開銷很小,但如果有很多線程 ,環境切換也可能嚴重
地影響程式的效能. 因此 ,頻繁地建立與關閉線程 ,極大地減低了系統的效能;如果使用線程
池 ,使線程能在多個請求中複用 , 則減少了系統已耗用時間,提高了系統的效率. 作者在 Mark
Grand 關於對象池模式的基礎上 ,通過對象池的擴充模式 ,設計了高效線程池. 研究了線程池
收稿日期:2003 - 04 - 04
作者簡介:朱 翔 1976 - ,男,湖南邵陽人,湖南移動通訊公司工程師.
中的死結問題、線程分配問題和線程泄漏問題 ,並提出了相應的解決方案.
1 相關原理
1. 1 對象池模式
Mark Grand 描述了對象池的設計模式 ,指出對象池是一種設計模式 ,它通過管理有限對
象複用來共用某些稀少或必須付出昂貴代價的資源[ 1] . 該模式的UML 結構分為3 部分 ,1
所示.
Reusable :被複用的對象或資源.
Client :複用對象的使用者. 1 個client 對象可以使用多個複用對象 ,1 個對象只能同時被 1
個client 使用.
ReusablePool :對象池,即複用對象的容器或集合,用來管理和儲存可以複用的對象. 一般來說,
為了保證對象複用的安全,對象池將按照singleton (單件模式) 的模式設計為全域唯一執行個體.
圖1 對象池UML 圖表
在對象池模式被提出以後 ,該模式被軟體設計人員廣泛使用 ,出現了串連池、線程池等 ,極
大地提高了系統的效能.
1. 2 對象池擴充模式
在開發過程中,發現在實際應用中對象池模式還存在著一些共同屬性 ,如複用對象的擴
展、對象池的管理、複用對象的生命週期管理等多項內容. 因此 ,在對象池模式的基礎上 ,根據
這些共性提出了對象池的擴充模式[2 ,3 ] . 見圖2 所示.
在該擴充模式中,新定義了6 個介面和2 個抽象擴充項物件. 其中,realObject Creater 介面負
責擴充複用對象的產生與消亡 ,編程人員可以通過實現 realObject Creater 抽象對象 ,將整個模
式快速擴充到新的應用領域; 而poolManager 介面負責抽象出對象的的管理原則,通過實現
poolManager 抽象對象 ,針對不同的對象實現不同的管理原則來完成複用對象的建立、消亡、管
理等工作. 作者在該擴充模式的基礎上 , 研究了線程池的產生與管理原則, 擴充了
realObject Creater 介面和poolManager 介面,實現了線程池的策略管理與線程的高效複用.
2 線程建立與消亡設計
realObject 是對象池擴充模式中用以擴充新的應用領域的抽象對象 ,其主要功能是完成對
象的建立與消亡. 線程的產生與消亡具有其特殊性 ,包括:
1) 線程是一個具有初始狀態、中間狀態、消亡狀態的特殊對象. 線程具有初始狀態意味著
線程在回收時,必須回到其初始狀態 ,而不能保留其中間狀態.
圖2 對象池擴充模式
2) 線程可能管理多種資源. 線上程消亡時必須釋放所有線程管理的資源.
當應用程式需要建立一個線程來完成某項任務時,線程池將建立一個新初始線程 ,但當任
務結束時,該線程並不會被立即銷毀,而是線上程池中被標識為暫停狀態 Suspended ;若應用
程式向線程池發出另一個線程請求 ,則處於暫停狀態的線程會被喚醒以完成相關任務,而不需
要建立一個新線程. 這樣可以節約大量資源. 只要應用程式向線程池發起請求的速度不超過一
個線程處理任務的速度 ,線程池就不需要為該應用程式建立新線程而一直重用同一個線程.
此外 ,若應用程式的工作負載下降或消失,此時,線程池中會有一些一直處於暫停狀態的
線程 ,線程池必須正確銷毀不再使用的線程. 針對這個問題 ,其演算法是:當處於暫停狀態的線程
在經過指定時間之後 ,若仍沒有相關的任務賦予該線程 ,則該線程會被喚醒 ,然後自我消亡 ,釋
放所有的資源.
總之 ,使用線程池技術 ,若應用程式需要處理的任務較多,線程池會建立足夠多的線程;而
若應用程式的負載下降,線程池能自動銷毀這些線程. 從而使作業系統的資源佔用保持在一個
合理的水平.
不同的線程有不同的初始化狀態 ,也有不同的資源釋放方式 ,必須把線程對象這2 部分的
功能抽象出來成為一個介面,使線程池可以管理線程對象的初始化與資源釋放 ,其介面如下所
示:
interface threadObject {
init () ; / / 線程初始化
releaseAllResource () ;/ / 釋放所有的資源
destroy () ;/ / 線程消亡
}
3 線程池管理原則
對線程的管理是線程池的重要組成部分,不同的管理對象需用不同的管理原則. 線程有其
使用的特殊性 ,在擴充poolManager 中,必須考慮線程的特殊性 ,如線程的死結問題 ,線程的資
源分配問題等.
3 . 1 線程死結與解決方案策略
死結是當一組線程中的每一個都在等待一個只有該組中另一個進程才能引起的事件時,
這組線程就無法正常運行 ,這種現象就稱為死結. 如在一個java 程式中,線程 A 持有對象 X 的
獨佔鎖 ,並且在等待對象 Y 的鎖 ,而線程 B 持有對象 Y 的獨佔鎖 ,卻在等待對象 X 的鎖[4 ] .
除非有某種方法來打破對鎖的等待 (J ava 鎖定不支援這種方法) ,否則死結的線程將永遠等下
去. 使用線程池還可能存在著另一種死結可能,即當所有池線程都在執行已阻塞的等待隊列中
某一個線程的執行結果,但這一任務卻因為未被佔用的線程而不能運行.
任何多線程應用程式都有可能導致死結. 對於一般的死結情況 ,必須找到一個機制來打破
這個鏈中的某一個線程所持有的資源 ,以解決其他線程對資源的渴望. 為此 ,利用管理對象中
的時間軸程 (該線程擁有最高優先順序) ,在指定的時間間隔內查看線程池中線程的“開始使用時
間”, 計算出該線程的使用時間,如果該線程的使用時間超過了設定的額定值 ,就釋放該線程
掌握的所有資源 ,並使它退回到起始狀態 ,從而使其他線程獲得啟動並執行時機. 該策略的偽碼如
下:
checkDeadLock {
lock ThreadPool () ; / / 鎖定線程池 ,不允許新的線程分配
for (對已用對象池中的每一個對象) {
if (object . uesTime > maxUesTime) / / 如果該線程的使用時間超過了設定的額定值
{
(( threadObject ) object) . releasAllResource() ;/ / 釋放該線程掌握的所有資源
((threadObject) object) . init() ;/ / 退回到起始狀態
usePool. release( object );
nuusePool. add ( object );
}
}
}
3 . 2 線程資源分派策略
相對於其他替代調度機制而言,線程池通常執行得很好. 但線程消耗包括記憶體和其他系統
資源在內的大量資源 ,因為每個線程都需要兩個可能很大的執行呼叫堆疊;雖然線程之間切換
的調度開銷很小,但如果有很多線程 ,環境切換也可能嚴重影響程式效能. 為此 ,採用2 種策略
來調節線程池的大小:
1) 設定線程池的最大使用線程量. 當線程池中的線程數量達到該額定值時,將其後的所
有請求排隊,等待線程池中出現新的空閑線程.
2) 定義線程的最大空閑時間. 如果某個線程的空閑時間超過了額定值 ,表明此時系統對
線程的需求不大 ,系統應該釋放部分線程以回收資源 ,使得系統的其他部分可以獲得更多的資
源使用. 要主要的是,必須保證線程的數量在最小額定值之上 ,否則就失去了線程池的意義. 其
程式的偽碼如下:
checkUnusePool {
for ( 每個空閑線程 ) {
if ( (空閑時間 > 最大空閑時間 ) and (空閑數量 > 最小線程數) {
((threadObject) object) . release() ;
((threadObject) object) . destroy() ;
}
}
}
3 . 3 線程泄漏
線程池容易出現的問題是線程泄漏. 所謂線程泄漏 ,就是本來應該被線程池管理的線程因
為某種原因無法被線程池訪問到,當這種情況發生的次數足夠多時,線程池最終就為空白,而且
系統將停止 ,因為沒有可用的線程來處理任務. 線程泄漏的主要原因包括:
1) 線程拋出一個 RuntimeException 或一個 Error 時,線程池沒有響應這個錯誤 ,或者用
戶沒有處理這個錯誤 ,那麼線程就會退出,線程池中線程的數量就會比實際數量多一個[4 ] . 這
可能導致線程池將這個實際不存在的線程分配出去 ,導致系統的錯誤. 為此 ,需時間軸程查看
所有的線程是否可用 ,如果發現某個線程實際已經不存在 ,而線程池還保留了它的“引用”或
指標 ,就必須將這些“引用”或指標釋放.
2) 線程可能會永遠等待某些資源或來自使用者的輸入 ,而這些資源又不能保證可用 ,諸如
此類的任務會永久停止 ,而這些停止的任務也會引起與線程泄漏同樣的問題. 這個問題和在死
鎖解決方案策略中遇到的問題相同,可採用同樣策略解決.
4 結 論
線程池使線程得到了高效複用,被廣泛應用於大型並發程式的設計開發. 在實際應用中,
採用時間軸程、控制線程池大小、查詢線程狀態的方法解決線程池中的死結問題、線程分配問
題和線程泄漏問題 ,取得了較好的效果.
參考文獻:
[ 1] Mark Grand. Patterns in Java[M ] . Wiley :John & Sons , 1999.
[2 ] 水 超 ,李 慧. 對象池模式的擴充與研究[J ] . 電腦工程 ,2003 , ( 1) :76 - 79.
[3 ] Erich Gamma , Richard Helm , Ralph Johnson , et al. Design Patterns Element s of Reusable Object - Oriented Software[M ]
. 北京:機械工業出版社,2002 .
[4 ] Brian Goetz. 線程池有助於實現最佳資源使用率[ EB/ OL ] . http :/ / www - 900 . ibm. com/ developerWorks/ cn/java/ j -
jtp0730/ index. shtml ,2002 - 10 - 15.