【Java 並發】Executor架構機制與線程池配置使用
一,Executor架構
Executor架構便是Java 5中引入的,其內部使用了線程池機制,在java.util.cocurrent 包下,通過該架構來控制線程的啟動、執行和關閉,可以簡化並發編程的操作。因此,在Java 5之後,通過Executor來啟動線程比使用Thread的start方法更好,更易管理,效率更好(用線程池實現,節約開銷)。
Executor架構主要包括:Executor,Executors,ExecutorService,AbstractExecutorService,ThreadPoolExecutor,CompletionService,Future,Callable等。
Executor
public interface Executor { void execute(Runnable command);}
Executor是一個簡單的介面,但卻是整個執行架構的基礎,將任務的提交和執行分解開,並用Runnable 來表示任務,所以Executor也可看成基於生產者-消費者模式,提交任務的操作相當於生產者,執行任務的線程相當於消費者。Executor的實現還提供了對生命週期的支援,以及統計資訊收集,應用程式管理機制和效能監控等機制。
ExecutorService
public interface ExecutorService extends Executor {void shutdown();List<Runnable> shutdownNow();boolean isShutdown();boolean isTerminated();boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;//........其他方法
ExecutorService擴充Executor,是一個比Executor使用更廣泛的子類介面,其提供了生命週期管理的方法,以及可跟蹤一個或多個非同步任務執行狀況返回Future的方法。它主要包括三種狀態:運行、關閉、終止。建立後便進入運行狀態,當調用了shutdown()方法時,便進入平緩關閉狀態,ExecutorService不再接受新的任務,但它還在等待已經提交的任務完成,shutdownNow()就簡單粗暴,直接嘗試關閉所有任務,並不在接收任務。
AbstractExecutorService
ExecutorService執行方法的預設實現
ThreadPoolExecutor
線程池,可以通過調用Executors以下靜態Factory 方法來建立線程池並返回一個ExecutorService對象。
Executors
提供了一系列靜態Factory 方法用於建立各種線程池:
1,newFixedThreadPool(int nThreads):建立一個線程池,該線程池重新使用一個固定數量的線程,該線程在共用的無界隊列中運行。在任何時候,大多數線程都將是活動的處理任務。如果在所有線程都處於活動狀態時提交額外的任務,它們將在隊列中等待,直到有一個線程可用。如果任何線程由於在關閉前的執行過程中出現故障而終止,那麼在需要執行後續任務時,將會有一個新的線程被執行。池中的線程將存在,直到ExecutorService顯式地執行服務關閉。
2,newCachedThreadPool():建立一個線程池,根據需要建立新的線程,但在可用時將重用以前構造的線程。這些池通常會提高執行許多短期非同步任務的程式的效能。如果可用,對execute的調用將重用以前構造的線程。如果沒有可用的現有線程,將建立一個新線程並將其添加到池中。在60秒內沒有使用的線程被終止並從緩衝中刪除。因此,一個閑置時間足夠長的池將不會消耗任何資源。具體逾時時間可設定。
3,newSingleThreadExecutor() :建立一個執行器,該執行程式使用單個背景工作執行緒來操作一個無界隊列。(請注意,如果此單線程由於在關閉前執行失敗而終止,則在需要執行後續任務時將會出現一個新的線程。)任務被保證按順序執行,並且在任何給定的時間內都不會有一個任務是活動的。與其他等效的newFixedThreadPool(1)不同,返回的執行程式保證不會重新設定,以使用其他線程。
4, newScheduledThreadPool(int corePoolSize):建立一個線程池,該線程池可以調度命令在給定的延遲之後運行,或者定期執行。類似於Timer。
一般來說,CachedTheadPool在程式執行過程中通常會建立與所需數量相同的線程,然後在它回收舊線程時停止建立新線程,因此它是合理的Executor的首選,只有當這種方式會引發問題時(比如需要大量長時間連線導向的線程時),才需要考慮用FixedThreadPool。——《Thinking in Java》第四版
二,線程池配置使用
雖然Executor架構可以高效的管理線程,但是並非所有任務都適合Executor所有執行策略,這也會降低效能和效率。
1,線程饑餓死結
只要線程池中的任務需要無限期的等待一些必須由池中其他任務才能提供的資源或者條件,但是該任務卻還在工作隊列中還未有線程去執行,這就會造成線程饑餓死結。舉個栗子,假設只有單線程Executor,此時A任務正在運行,A任務等待B任務的結果才可繼續,於是就等待,但是此時是單線程,B任務一直無法運行,它在等A任務結束。於是兩個任務互相等待,造成死結。在更大的線程池中也能出現,除非線程池足夠大。
提交這種有依賴性的Executor,就需要避免饑餓死結,因此需要考慮線程池大小限制或者配置限制。
2,運行長時間任務
執行長時間的任務不僅會造成線程池阻塞,甚至還會增加執行時間短任務的服務時間,降低了Executor管理的服務的響應性。
緩解長時間任務造成的影響,可以限定任務等待資源的時間。如果等待逾時,那麼標記任務失敗,然後中止任務或者稍後再執行任務。這種辦法保證任務總能繼續執行下去。如果線程池總是阻塞,那麼就要考慮線程池是否設定過小。
關於設定線程池的大小估算可以看這篇博文
http://ifeve.com/how-to-calculate-threadpool-size/
ThreadPoolExecutor提供了構造方法,可以根據需求配置線程池,進行定製
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) //後兩個參數為選擇性參數
具體的ThreadPoolExecutor參數配置
corePoolSize
核心線程數,核心線程會一直存活,即使沒有任務需要處理。當線程數小於核心線程數時,即使現有的線程空閑,線程池也會優先建立新線程來處理任務,而不是直接交給現有的線程處理。核心線程在allowCoreThreadTimeout被設定為true時會逾時退出,預設情況下不會退出。
maximumPoolSize
當線程數大於或等於核心線程,且任務隊列已滿時,線程池會建立新的線程,直到線程數量達到maxPoolSize。如果線程數已等於maxPoolSize,且任務隊列已滿,則已超出線程池的處理能力,線程池會拒絕處理任務而拋出異常。
keepAliveTime:線程池維護線程所允許的空閑時間
unit: 線程池維護線程所允許的空閑時間的單位
workQueue: 線程池所使用的緩衝隊列
handler: 線程池對拒絕任務的處理策略
當一個任務通過execute(Runnable)方法欲添加到線程池時:
1.當線程池小於corePoolSize時,新提交任務將建立一個新線程執行任務,即使此時線程池中存在空閑線程。
2.當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行 。
3.當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會建立新線程執行任務。
4.當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理 。
5.當線程池中超過corePoolSize線程,空閑時間達到keepAliveTime時,關閉空閑線程
6.當設定allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閑時間達到keepAliveTime也將關閉。
以上都是閱讀《Java編程思想》-並發和《Java並發編程實踐》的筆記,不喜勿噴。