為什么要使用線程池?
為了減少創建和銷毀線程的次數,讓每個線程都可以多次的使用,可以根據系統情況調整線程的數量,防止消耗過多內存。在實際使用中,服務器在創建和銷毀線程上花費的時間和消耗的系統資源都相當大,使用線程池就可以優化。
在java中,如果每個請求到達就創建一個新線程,開銷是相當大的。在實際使用中,服務器在創建和銷毀線程上花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了創建和銷毀線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm里創建太多的線程,可能會使系統由於過度消耗內存或“切換過度”而導致系統資源不足。為了防止資源不足,服務器應用程序需要采取一些辦法來限制任何給定時刻處理的請求數目,盡可能減少創建和銷毀線程的次數,特別是一些資源耗費比較大的線程的創建和銷毀,盡量利用已有對象來進行服務,這就是“池化資源”技術產生的原因。
線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務重復使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由於在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即為請求服務,使用應用程序響應更快。另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。
一:線程池參數簡介
ThreadPoolExecutor類可設置的參數主要有:
corePoolSize:核心線程
1.核心線程會一直存活,及時沒有任務需要執行
2.當線程數小於核心線程數時,即使有線程空閑,線程池也會優先創建新線程處理
3.設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關閉
queueCapacity:任務隊列容量(阻塞隊列)
當核心線程數達到最大時,新任務會放在隊列中排隊等待執行
maxPoolSize:最大線程數
1.當線程數>=corePoolSize,且任務隊列已滿時。線程池會創建新線程來處理任務
2.當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而拋出異常
keepAliveTime:線程空閑時間
1.當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數量=corePoolSize
2.如果allowCoreThreadTimeout=true,則會直到線程數量=0
rejectedExecutionHandler:任務拒絕處理器
兩種情況會拒絕處理任務:
1.當線程數已經達到maxPoolSize,切隊列已滿,會拒絕新任務
2.當線程池被調用shutdown()后,會等待線程池里的任務執行完畢,再shutdown。如果在調用shutdown()和線程池真正shutdown之間提交任務,會拒絕新任務。
線程池會調用rejectedExecutionHandler來處理這個任務。如果沒有設置,默認是AbortPolicy,會拋出異常。
ThreadPoolExecutor類有幾個內部實現類來處理拒絕任務:
1.AbortPolicy 丟棄任務,拋運行時異常
2.CallerRunsPolicy 執行任務
3.DiscardPolicy 忽視,什么都不會發生
4.DiscardOldestPolicy 從隊列中踢出最先進入隊列(最后一個執行)的任務
5.實現RejectedExecutionHandler接口,可自定義處理器
二:ThreadPoolExecutor執行順序:
1.當線程數小於核心線程數時,創建線程。
2.當線程數大於等於核心線程數,且任務隊列未滿時,將任務放入任務隊列。
3.當線程數大於等於核心線程數,且任務隊列已滿
3.1若線程數小於最大線程數,創建線程
3.2若線程數等於最大線程數,拋出異常,拒絕任務
三:線程池參數的合理設置
為了說明合理設置的條件,我們首先確定有以下幾個相關參數:
1.tasks,程序每秒需要處理的最大任務數量(假設系統每秒任務數為100~1000)
2.tasktime,單線程處理一個任務所需要的時間(每個任務耗時0.1秒)
3.responsetime,系統允許任務最大的響應時間(每個任務的響應時間不得超過2秒)
corePoolSize
每個任務需要tasktime秒處理,則每個線程每秒可處理1/tasktime個任務。系統每秒有tasks個任務需要處理,則需要的線程數為:tasks/(1/tasktime)。
即tasks*tasktime個線程數。假設系統每秒任務數為100到1000之間,每個任務耗時0.1秒,則需要100x0.1至1000x0.1,即10到100個線程。那么corePoolSize應該設置為大於10。
具體數字最好根據8020原則,即80%情況下系統每秒任務數,若系統80%的情況下任務數小於200,最多時為1000,則corePoolSize可設置為20。
queueCapacity:任務隊列的長度
任務隊列的長度要根據核心線程數,以及系統對任務響應時間的要求有關。隊列長度可以設置為(corePoolSize/tasktime)responsetime: (20/0.1)2=400,即隊列長度可設置為400。
如果隊列長度設置過大,會導致任務響應時間過長,如以下寫法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
這實際上是將隊列長度設置為Integer.MAX_VALUE,將會導致線程數量永遠為corePoolSize,再也不會增加,當任務數量陡增時,任務響應時間也將隨之陡增。
maxPoolSize:最大線程數
當系統負載達到最大值時,核心線程數已無法按時處理完所有任務,這時就需要增加線程。每秒200個任務需要20個線程,那么當每秒達到1000個任務時,則需要(1000-queueCapacity)*(20/200),即60個線程,可將maxPoolSize設置為60。
keepAliveTime:
線程數量只增加不減少也不行。當負載降低時,可減少線程數量,如果一個線程空閑時間達到keepAliveTiime,該線程就退出。默認情況下線程池最少會保持corePoolSize個線程。keepAliveTiime設定值可根據任務峰值持續時間來設定。
以上關於線程數量的計算並沒有考慮CPU的情況。若結合CPU的情況,比如,當線程數量達到50時,CPU達到100%,則將maxPoolSize設置為60也不合適,此時若系統負載長時間維持在每秒1000個任務,則超出線程池處理能力,應設法降低每個任務的處理時間(tasktime)。