前提
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群
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/523166.html
標籤:其他
上一篇:淺談PHP設計模式的責任鏈模式
