Executor框架
Executor
基於生產——消費者模式,提交任務相當於生產者,執行任務的線程相當於消費者。如果要在程序中實現生產者——消費者,那麼最簡單的方式就是使用Executor。
線程池中的兩個重要角色是工作隊列(Work Queue)和工作線程(Worker Thread)。生產者向工作隊列中添加任務,消費者(工作線程)從工作隊列中獲取任務並執行,任務執行完成後,返回線程到線程池。
- Executor 最頂層的類,解耦Runnable的提交和執行
- ExecutorService 提供生命週期方法shutdown(),shutdownNow()…,和Callable任務提交
- ScheduledExecutorService 對ExecutorService擴展了定時調用
- AbstractExecutorService 一些基本實現,會發現submit()本質上還是調用的是execute()方法,有點像模板方法
- ThreadPoolExecutor 大量具體的實現,包括Executor#execute
- ScheduledThreadPoolExecutor 定時調度線程池的實現
Executors
Executors是Executor的工廠,分以下幾類方法:
- 創建和返回ExecutorService
- 創建和返回ScheduledExecutorService
- 創建和返回不可修改配置的線程池,這種線程池一旦創建完成後,後續的其他代碼是不能修改線程池參數的。
- 創建和返回ThreadFactory
- 創建和返回Callable,將Runnable適配爲Callable
Executors提供了常用策略的線程池創建,當然如果你也可以直接使用ThreadPoolExecutor
或ScheduledThreadPoolExecutor
創建你想要的線程池。
-
newFixedThreadPool
創建一個固定長度的線程池/* * @param nThreads the number of threads in the pool * @return the newly created thread pool * @throws IllegalArgumentException if {@code nThreads <= 0} */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
另一個對應的工程方法需要提供ThreadFactory,用於線程池創建線程
-
newCachedThreadPool
緩存線程池/* * @return the newly created thread pool */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
另一個對應的工程方法需要提供ThreadFactory,用於線程池創建線程
-
newSingleThreadExecutor
單線程的Executor/* * @return the newly created single-threaded Executor */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
另一個對應的工程方法需要提供ThreadFactory,用於線程池創建線程
-
newScheduledThreadPool
定時調度的線程池public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
另一個對應的工程方法需要提供ThreadFactory,用於線程池創建線程
-
newSingleThreadScheduledExecutor
單線程定時調度線程池
public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); }
另一個對應的工程方法需要提供ThreadFactory,用於線程池創建線程
歸根結底還是配置ThreadPoolExecutor
和ScheduledThreadPoolExecutor
,所以上面不詳細解釋每個工廠方法的特性,只要明白了ThreadPoolExecutor
和ScheduledThreadPoolExecutor
配置一切都會明白。
配置ThreadPoolExecutor
ThreadPoolExecutor通用構造函數
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
-
線程的創建與銷燬
corePoolSize
沒有任務執行時大的線程池的大小,並且只有工作隊列workQueue
滿了纔會創建超出這個數量的線程,但不會超過maximumPoolSize
。在創建ThreadPoolExecutor初期,線程並不會立即啓動,而是等待任務提交時纔會啓動,除非調用
prestartAllCoreThreads
。maximumPoolSize
表示可同時活動的線程數量上限。
當某個線程的空閒時間(沒有任務運行)超過存活時間keepAliveTime
,那麼這個線程將被標記爲可回收,並且線程池的當前大小超過了corePoolSize
時,這個線程將被終止。
newFixedThreadPool
的工廠方法的corePoolSize
和maximumPoolSize
一樣大,keepAliveTime
爲0,所以線程池會一直保持設置的大小,不擴展也不回收。
newCachedThreadPool
的工廠方法的corePoolSize
爲0和maximumPoolSize
爲Integer.MAX_VALUE
,keepAliveTime
爲60s,是一個無界線程池,線程空閒會被回收。如果把
corePoolSize
設置爲0(像newCachedThreadPool
),工作隊列改爲其他的非SynchronousQueue
工作隊列,例如大小爲20LinkedBlockingQueue
,那麼只有等工作隊列填滿20任務後,纔開始執行。 -
管理隊列任務
BlockingQueue是一個阻塞隊列,阻塞隊列提供了put
和take
方法,以及支持定時的offer
和poll
方法。如果隊列已經滿了,那麼put
方法將阻塞直到有空間可用;如果隊列爲空,那麼take方法將阻塞直到元素可用。隊列可以是有界的也可以是無界的,無界隊列永遠都不會充滿,因此無解隊列的put
永遠不會阻塞。基本的任務排隊方法有:無解隊列、有界隊列、和同步移交(Synchronous Handoff)。
newFixedThreadPool
和newSingleThreadExecutor
使用無界的LinkedBlockingQueue
,默認構造的大小是Integer.MAX_VALUE
/** * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
更穩妥的策略是使用有界隊列避免資源耗盡的情況發生,例如使用
ArrayBlockingQueue
、有界的LinkedBlockingQueue
(構造傳值)、PriorityBlockingQueue
(這是一個優先隊列,構造傳入Comparator
)。但是隊列滿了怎麼辦?這時的飽和策略由RejectedExecutionHandler
實現決定。
對於非常大或者無界的線程池,可以使用SynchronousQueue
來避免排隊(同步移交Synchronous Handoff),直接將任務從生產者移交給工作線程。SynchronousQueue
不是一個真正的隊列,而是線程之間移交的機制。要將元素放入SynchronousQueue
中,必須有一個線程正在等待接受這個元素。如果沒有線程等待,並且線程池的當前大小小於maximumPoolSize
,那麼ThreadPoolExecutor
會創建一個新線程。當超過maximumPoolSize
時,根據飽和策略被拒絕。只有線程池是無界的或任務可以被拒絕時,SynchronousQueue
纔有價值。例如,newCachedThreadPool
的maximumPoolSize
爲Integer.MAX_VALUE
。 -
飽和策略
JDK提供了幾種不同的RejectedExecutionHandler
實現,每種都包含不同的飽和策略:AbortPolicy
、CallerRunsPolicy
、DiscardPolicy
、DiscardOldestPolicy
。
AbortPolicy
是默認的飽和策略,該策略會拋出未檢查異常RejectedExecutionException
。
DiscardPolicy
當新提交的任務無法保存到隊列中等待執行時,會拋棄該任務。
DiscardOldestPolicy
會拋棄下一個被執行的任務,然後重新提交新任務。不能和PriorityBlockingQueue
使用,因爲會拋棄優先級最高的任務。
CallerRunsPolicy
該策略實現一直調節機制,不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者,從而降低任務流量。當線程池滿,工作隊列滿,它會把新任務交給調用者(執行execute任務提交的線程)的線程執行。任務提交的線程也變成了一個執行任務的線程,可以緩解任務提交量。 -
線程工廠
你可以實現ThreadFactory,自定義一個線程工廠,指定一個有意義的線程名,或者給線程設置UncaughtExceptionHandler等等。
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}