前提 java version "1.8.0_25" 池簡述 軟體開發活動中,我們經常會聽到資料庫連接池、記憶體池、線程池等各種“池”概念,這些“池”到底是什麼東西呢?程式的世界里,我們可以將池簡單的理解為一種容器類數據結構,比如列表。程式處理信息的過程中,可能會依賴某些資源或者對象(暫且統一稱之為對 ...
前提
java version "1.8.0_25"
池簡述
軟體開發活動中,我們經常會聽到資料庫連接池、記憶體池、線程池等各種“池”概念,這些“池”到底是什麼東西呢?程式的世界里,我們可以將池簡單的理解為一種容器類數據結構,比如列表。程式處理信息的過程中,可能會依賴某些資源或者對象(暫且統一稱之為對象),比如資料庫連接,來執行一些高頻操作,比如數據表查詢,此時,如果被依賴對象的存活時間比較短,那就意味著需要頻繁的創建和銷毀對象,這可能會很耗時、耗系統資源(CPU、記憶體、磁碟、網路等)。為瞭解決這個問題,進行程式設計時,可能會考慮在程式初始化時,預先創建一批所需對象,並存儲到池中,或者根據需要即時創建對象,併在使用完成後,將對象添加到池中,這樣,當程式需要(再次)使用對象時,可以直接從池中直接獲取現有的對象,節省了頻繁創建和銷毀對象帶來的資源浪費,這就是池的作用,為程式提供復用對象或者提前分配資源的能力。
ThreadPoolExecutor線程池介紹
下文僅針對線程池的一些要點做介紹
任務處理流程
核心線程池大小(corePoolSize
)和最大線程池大小(maximumPoolSize
)
ThreadPoolExecutor
會根據corePoolSize
(保持存活(不允許超時退出等)的最小工作線程數,如果設置了allowCoreThreadTimeOut
為true
,則該值為0。可通過getPoolSize
方法獲取該值) 和maximumPoolSize
(線程池中允許的最大線程數,可通過getMaximumPoolSize
獲取該值)設置的界限自動調整線程池的大小。
當通過execute(Runnable)
方法提交新任務後,如果正在運行的線程的數量小於corePoolSize
,則創建新線程來處理請求,即使存在其它空閑的工作線程,否則如果正在運行的線程的數量大於corePoolSize
,但小於maximumPoolSize
,則僅僅在隊列已經滿時才會創建新線程來處理請求。設置corePoolSize
等於maximumPoolSize
則表示創建一個固定大小的線程池。
通過設置maximumPoolSize
為基本無界的值,例如Integer.MAX_VALUE
,則允許線程池容納任意併發任務數。大多數情況下,corePoolSize
和maximumPoolSize
僅在構建時設置,但也可以分別用使用setCorePoolSize
和setMaximumPoolSize
對其進行動態更改。
按需創建線程
預設情況下,僅在新任務到達時創建和啟動線程,即便是核心線程。可以使用prestartCoreThread
或者prestartAllCoreThreads
對此進行動態更改。如果使用非空隊列構造線程池,你可能會想預先啟動線程。
創建新線程
使用ThreadFactory
創建新線程。如果未指定,則使用Executors.defaultThreadFactory
,其創建的線程都位於相同線程組,且擁有相同的優先順序NORM_PRIORITY
以及非守護狀態。通過提供不同的線程工程ThreadFactory
,可以修改線程的名稱,線程組,優先順序,守護狀態等等。當newThread
返回null
時,ThreadFactory
將無法創建線程,此時執行器繼續運行,但是可能無法執行任何任務。線程應該擁有modifyThread RuntimePermission
。如果工作線程或者其它線程使用不具有該許可權的線程池,服務可能被降級:配置變更可能不會及時生效,且關閉線程池可能會保留終止但未完成的狀態。
線程保持存活時間
如果線程池當前擁有多於corePoolSize
數量的線程,則空閑時間超過keepAliveTime
(可通過getKeepAliveTime(TimeUnit)
方法獲取)的線程將被終止,以減少資源消耗。可以通過setKeepAliveTime(long,TimeUnit)
方法動態改變該參數值。使用setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)
可以有效的阻止空閑線程在關閉之前終止。預設情況下,keep-alive
策略僅線上程池中線程數多餘corePoolSize
時起作用。keepAliveTime
的值不為0的情況下,可通過allowCoreThreadTimeOut(boolean)
方法將keep-alive
策略應用於核心線程。
排隊(Queuing)
BlockingQueue
用於傳輸和容納提交的任務。此隊列的使用與線程池大小變化相關:
- 如果線程池中當前線程數少於
corePoolSize
,那麼Executor
總是優先創建新線程來處理任務請求,而不是讓任務請求排隊 - 如果線程池中當前線程數等於或者多餘
corePoolSize
,那麼Executor
總是優先讓任務排隊,而不是創建新線程 - 如果無法讓任務請求排隊(比如任務隊列已滿),且線程池中當前線程數未超過
maximumPoolSize
,則創建一個新線程來處理任務請求,否則將拒絕該任務請求
三種排隊策略:
-
直接傳遞(Direct handoffs)
SynchronousQueue
是工作隊列(workQueue
)的一個預設好選擇。它將任務交給線程,而不是保留它們。此時,如果沒有立即可用的線程,將構造新線程,因為讓任務排隊的嘗試將會失敗。此策略在處理可能具有內部依賴關係的請求集時避免鎖定。通常需要無界的maximumPoolSize
,以避免拒絕新任務的提交。這反過來說明當任務平均提交速度持續大於平均處理速度時,線程數無限增長的可能性。如果使用newCachedThreadPool
創建線程池則表示使用直接傳遞策略 -
無界隊列(Unbounded queues)
當所有核心線程都繁忙時,使用無界隊列(例如,沒有預定義容量的
LinkedBlockingQueue
)將導致新任務在隊列中等待,從而導致沒有多餘corePoolSize
的線程被創建(maximumPoolSize
的值不起任何作用)。當每個任務完全彼此獨立,互不影響執行時,這可能是合適的。例如,在網頁伺服器中, 這種排隊方式用於平滑瞬時大量請求時很有用。需要註意的是,當任務平均提交速度持續大於平均處理速度時,可能會導致無界隊列無限增長。如果使用newFixedThreadPool
創建線程池則表示使用無界隊列。 -
有界隊列(Bounded queues)
有界隊列(例如,
ArrayBlockingQueue
)配合maximumPoolSizes
使用有助於防止資源耗盡,但是難以調整和控制。隊列大小和最大線程池大小需要相互權衡:使用大隊列和較小的線程池可以最大限度地減少CPU使用率,操作系統資源和上下文切換開銷,但是會導致人為的低吞吐量。如果任務頻繁被阻塞(比如I/O限制),那麼系統可以調度比我們允許的更多的線程。使用小隊列通常需要較大的線程池,這會讓CPU保持繁忙,但可能會產生不可接受的調度開銷,這也會降低吞吐量。
拒絕處理任務
當Executor
已關閉、使用有界的線程池、工作隊列,且達到最大值時,通過方法execute(Runnable)
提交的任務將被拒絕。在任何一種情況下,execute
方法調用其RejectedExecutionHandler
的rejectedExecution(Runnable,ThreadPoolExecutor)
方法。提供以下4種預定義處理策略:
ThreadPoolExecutor.AbortPolicy
(預設策略)
拒絕任務時,處理器會拋出一個運行時異常RejectedExecutionException
。
ThreadPoolExecutor.CallerRunsPolicy
調用execute
的線程自己運行任務。這提供了一個簡單的反饋控制機制,將會降低新任務提交的速率。
ThreadPoolExecutor.DiscardPolicy
不能被執行的任務將被拋棄
ThreadPoolExecutor.DiscardOldestPolicy
如果Executor
已關閉,工作隊列隊首的任務被丟棄,然後重試執行。(重試也可能失敗,導致重覆執行前面的動作)
可以定義和使用其他類型的RejectedExecutionHandler
類。這樣做需要一些謹慎,特別是當策略被設計為僅在特定容量或者隊列策略下有效時
線程運行狀態
該線程池使用了一個runState
來對線程進行主要生命周期控制,具有以下值:
RUNNING
: 接收新任務並且處理排隊的任務
SHUTDOWN
: 不接收新任務,但是處理排隊的任務。
STOP
: 不接收新任務,不處理排隊的任務,並且中斷正在進行的任務。
TIDYING
: 所有任務已終止。workerCount
為0。線程轉為TIDYING
狀態將會運行terminated()
hook方法。
TERMINATED
: terminated()
已經運行完。
這些值之間的數字順序很重要,為了支持有序比較,runState
會隨著時間單調遞增,但不需要達到每個狀態。
狀態轉換如下:
RUNNING
-> SHUTDOWN
調用shutdown()
時,可能隱式的在finalize()
中調用
RUNNING
或者 SHUTDOWN
-> STOP
調用shutdownNow()
時
SHUTDOWN
-> TIDYING
當工作隊列和線程池都為空時
STOP
-> TIDYING
線程池為空時
TIDYING
-> TERMINATED
當terminated()
hook方法運行完成時。
線程的析構(Finalization)
如果線程池不再被程式引用且沒有剩餘的線程,線程池將被關閉。如果希望確保未被引用的線程池被回收,即使用戶用戶忘記調用shutdown
,則必須通過適當的keep-alive配置,使用更低的下限--0核心線程數或者設置allowCoreThreadTimeOut(boolean)
,確保未使用的線程最終會消亡。
作者:授客
微信/QQ:1033553122
全國軟體測試QQ交流群:7156436
Git地址:https://gitee.com/ishouke
友情提示:限於時間倉促,文中可能存在錯誤,歡迎指正、評論!
作者五行缺錢,如果覺得文章對您有幫助,請掃描下邊的二維碼打賞作者,金額隨意,您的支持將是我繼續創作的源動力,打賞後如有任何疑問,請聯繫我!!!
微信打賞
支付寶打賞 全國軟體測試交流QQ群