線程池的好處:
1,因為線程是比較昂貴的資源,避免大量重復創建銷毀線程,使用者不用關心創建銷毀線程。
2,用戶提交的任務能夠及時的得到處理,提高響應速度。
3,能夠更好的監控和管理線程。
ThreadPoolExecutor參數
- int corePoolSize
- 線程池基本大小
- int maximumPoolSize
- 線程池最大大小
- long keepAliveTime
- 保持活動時間
- TimeUnit unit
- 保持活動時間單位
- BlockingQueue workQueue
- 工作隊列
- ThreadFactory threadFactory
- 線程工廠
- RejectedExecutionHandler handler
- 駁回回調
這些參數這樣描述起來很空洞,下面結合執行任務的流程來看一下。
ThreadPoolExecutor執行任務流程
當線程池大小 >= corePoolSize 且 隊列未滿時,這時線程池使用者與線程池之間構成了一個生產者-消費者模型。線程池使用者生產任務,線程池消費任務,任務存儲在BlockingQueue中,注意這里入隊使用的是offer,當隊列滿的時候,直接返回false,而不會等待。
keepAliveTime
當線程處於空閑狀態時,線程池需要對它們進行回收,避免浪費資源。但空閑多長時間回收呢,keepAliveTime就是用來設置這個時間的。默認情況下,最終會保留corePoolSize個線程避免回收,即使它們是空閑的,以備不時之需。但我們也可以改變這種行為,通過設置allowCoreThreadTimeOut(true)
。
workQueue
線程池所使用的緩沖隊列,該緩沖隊列的長度決定了能夠緩沖的最大數量,緩沖隊列有三種通用策略:
1) 直接提交。工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性;
2) 無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性;
3) 有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。
unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小時
TimeUnit.MINUTES; //分鍾
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //納秒
三種常用的 ThreadPoolExecutor
Executors 是提供了一組工廠方法用於創建常用的 ExecutorService ,分別是 FixedThreadPool,CachedThreadPool 以及 SingleThreadExecutor。這三種ThreadPoolExecutor都是調用 ThreadPoolExecutor 構造函數進行創建,區別在於參數不同。
FixedThreadPool - 線程池大小固定,任務隊列無界
下面是 Executors 類 newFixedThreadPool 方法的源碼:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
可以看到 corePoolSize 和 maximumPoolSize 設置成了相同的值,此時不存在線程數量大於核心線程數量的情況,所以KeepAlive時間設置不會生效。任務隊列使用的是不限制大小的 LinkedBlockingQueue ,由於是無界隊列所以容納的任務數量沒有上限。
因此,FixedThreadPool的行為如下:
-
從線程池中獲取可用線程執行任務,如果沒有可用線程則使用ThreadFactory創建新的線程,直到線程數達到nThreads
-
線程池線程數達到nThreads以后,新的任務將被放入隊列
FixedThreadPool的優點是能夠保證所有的任務都被執行,永遠不會拒絕新的任務;同時缺點是隊列數量沒有限制,在任務執行時間無限延長的這種極端情況下會造成內存問題。
SingleThreadExecutor - 線程池大小固定為1,任務隊列無界
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
這個工廠方法中使用無界LinkedBlockingQueue,並的將線程數設置成1,除此以外還使用FinalizableDelegatedExecutorService類進行了包裝。這個包裝類的主要目的是為了屏蔽ThreadPoolExecutor中動態修改線程數量的功能,僅保留ExecutorService中提供的方法。雖然是單線程處理,一旦線程因為處理異常等原因終止的時候,ThreadPoolExecutor會自動創建一個新的線程繼續進行工作。
SingleThreadExecutor 適用於在邏輯上需要單線程處理任務的場景,同時無界的LinkedBlockingQueue保證新任務都能夠放入隊列,不會被拒絕;缺點和FixedThreadPool相同,當處理任務無限等待的時候會造成內存問題。
CachedThreadPool - 線程池無限大(MAX INT),等待隊列長度為1
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
SynchronousQueue是一個只有1個元素的隊列,入隊的任務需要一直等待直到隊列中的元素被移出。核心線程數是0,意味着所有任務會先入隊列;最大線程數是Integer.MAX_VALUE,可以認為線程數量是沒有限制的。KeepAlive時間被設置成60秒,意味着在沒有任務的時候線程等待60秒以后退出。CachedThreadPool對任務的處理策略是提交的任務會立即分配一個線程進行執行,線程池中線程數量會隨着任務數的變化自動擴張和縮減,在任務執行時間無限延長的極端情況下會創建過多的線程。
- 為什么newFixedThreadPool中要將corePoolSize和maximumPoolSize設置成一樣? 答:因為newFixedThreadPool中用的是LinkedBlockingQueue(是無界隊列),只要當前線程大於等於corePoolSize來的任務就直接加入到無界隊列中,所以線程數不會超過corePoolSize,這樣maximumPoolSize沒有用。例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。
- 為什么newFixedThreadPool中隊列使用LinkedBlockingQueue?答:設置的corePoolSize 和 maximumPoolSize相同,則創建的線程池是大小固定的,要保證線程池大小固定則需要LinkedBlockingQueue(無界隊列)來保證來的任務能夠放到任務隊列中,不至於觸發拒絕策略。
- 為什么newFixedThreadPool中keepAliveTime會設置成0?因為corePoolSize和maximumPoolSize一樣大,KeepAliveTime設置的時間會失效,所以設置為0。
- 為什么newCachedThreadPool中要將corePoolSize設置成0?答:因為隊列使用SynchronousQueue,隊列中只能存放一個任務,保證所有任務會先入隊列,用於那些互相依賴的線程,比如線程A必須在線程B之前先執行。
- 為什么newCachedThreadPool中隊列使用SynchronousQueue?答:線程數會隨着任務數量變化自動擴張和縮減,可以靈活回收空閑線程,用SynchronousQueue隊列整好保證了CachedTheadPool的特點。
- 為什么newSingleThreadExecutor中使用DelegatedExecutorService去包裝ThreadPoolExecutor?答:SingleThreadExecutor是單線程化線程池,用DelegatedExecutorService包裝為了屏蔽ThreadPoolExecutor動態修改線程數量的功能,僅保留Executor中的方法。
參考網址:http://blog.csdn.net/ghsau/article/details/53538303
https://segmentfault.com/a/1190000008394155