Executor框架簡介
Executor框架的兩級調度模型
在HotSpot VM的線程模型中,Java線程被一對一映射爲本地操作系統線程,Java線程啓動時會創建一個本地OS線程;當該Java線程終止時,這個OS線程也會被回收。OS會調度所有線程並將它們分配給可用的CPU。
在上層,Java多線程程序通常把應用分解爲若干個任務,然後使用用戶級的調度器(Excutor框架)將這些任務映射爲固定數量的線程;在底層,OS內核(OSKernel)將這些線程映射到硬件處理器上。
由下圖可看出,應用程序通過Executor框架控制上層的調度;而下層的調度由操作系統 內核控制,下層的調度不受應用程序的控制。
Executor框架的結構與成員
Executor框架主要由3大部分組成:
- 任務。包括被執行任務需要實現的接口:Runnable接口或Callable接口
- 任務的執行。包括任務執行機制的核心接口Executor,以及繼承自Executor的 ExecutorService接口。Executor框架有兩個關鍵類實現了ExecutorService接口 (ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
- 異步計算的結果。包括接口Future和實現Future接口的FutureTask類
Executor框架包含的主要的類與接口如下所示,
(1)Executor是一個接口,它是Executor框架的基礎,它將任務的提交與任務的執行分離。
(2)ThreadPoolExecutor是線程池的核心實現類,用來執行被提交的任務。
(3)ScheduledThreadPoolExecutor是一個實現類,可以在給定的延遲後運行命令,或者定期執行命令。ScheduledThreadPoolExecutor比Timer更靈活,功能更強大。
(4)Future接口和實現Future接口的FutureTask類,代表異步計算的結果。
(5)Runnable接口和Callable接口的實現類,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。
Executor框架的使用
(1)主線程首先要創建實現Runnable或者Callable接口的任務對象。工具類Executors可以把一 個Runnable對象封裝爲一個Callable對象(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))。
(2)以把Runnable對象直接交給ExecutorService執行(ExecutorService.execute(Runnable command));或者也可以把Runnable對象或Callable對象提交給ExecutorService執行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callabletask))。
如果執行ExecutorService.submit(…),ExecutorService將返回一個實現Future接口的對象 。由於FutureTask實現了Runnable,程序員也可以創建FutureTask,然後直接交給ExecutorService執行。
(3)主線程可以執行FutureTask.get()方法來等待任務執行完成。主線程也可以執行 FutureTask.cancel(boolean mayInterruptIfRunning)來取消此任務的執行。
Executor框架的主要成員
(1)ThreadPoolExecutor
ThreadPoolExecutor通常使用工廠類Executors來創建。Executors可以創建3種類型的 ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。
1)FixedThreadPool
創建使用固定線程數的方法如下。它適用於爲滿足資源管理,而需要限制當前線程數量的應用場景。如負載較重的服務器。
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
2)SingleThreadExecutor
創建使用單個線程的方法如下。它適用於需要保證順序地執行各個任務;並且在任意時間點,不會有多 個線程是活動的應用場景。
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
3)CachedThreadPool
創建一個會根據需要而創建新線程的方法如下。它是大小無界的線程池,適用於執行很多的短期異步任務的小程序,或者 是負載較輕的服務器。
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
(2)ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor通常使用工廠類Executors來創建。Executors可以創建2種類型的ScheduledThreadPoolExecutor:
1)ScheduledThreadPoolExecutor
它適用於需要多個後臺線程執行週期任務,同時爲了滿足資源管理的需求而需要限制後臺線程數量的場景。它包含若干個線程
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize,ThreadFactory factory
2)SingleThreadScheduledExecutor
它適用於需要單個後臺線程執行週期任務,同時需要保證順序地執行各個任務的應用場景。
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor
(ThreadFactory threadFactory)
(3)Future接口
Future接口和實現Future接口的FutureTask類用來表示異步計算的結果。當我們把Runnable 接口或Callable接口的實現類提交(submit)給ThreadPoolExecutor或 ScheduledThreadPoolExecutor時,ThreadPoolExecutor或ScheduledThreadPoolExecutor會向我們 返回一個FutureTask對象。
<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable task, T result)
Future<> submit(Runnable task)
注:截止至JDK8爲止,Java通過上述API返回的是一個 FutureTask對象。但Java僅僅保證返回的是一個實現了Future接口的對象。在將 來的JDK實現中,返回的可能不一定是FutureTask。
(4)Runnable接口和Callable接口
Runnable接口和Callable接口的實現類,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。它們之間的區別是Runnable不會返回結果,而Callable可以返回結果。
可以使用工廠類Executors來把一個 Runnable包裝成一個Callable。
public static Callable<Object> callable(Runnable task)
我們也可以把一個Runnable和一個待返回的結果包裝成一個Callable
public static <T> Callable<T> callable(Runnable task, T result)
ThreadPoolExecutor詳解
Executor框架最核心的類是ThreadPoolExecutor,它是線程池的實現類,主要的參數如下:
- corePool:核心線程數量。 即使沒有任務執行,核心線程也會一直存活
- maximumPool:最大線程池的大小。
- keepAliveTime:如果當前線程池中線程數大於corePoolSize,則多餘的線程在等待keepAliveTime時間後如果還沒有新的線程任務指派給它、它就會被回收。
- BlockingQueue:用來暫時保存任務的工作隊列。
- RejectedExecutionHandler:當ThreadPoolExecutor已經關閉或ThreadPoolExecutor已經飽和 時(達到了最大線程池大小且工作隊列已滿),execute()方法將要調用的Handler。
ThreadPoolExecutor有3種類型·FixedThreadPool,SingleThreadExecutor,CachedThreadPool。下面分別介紹
FixedThreadPool詳解
它是可重用固定線程數的線程池,構造它的靜態方法如下
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool的corePoolSize和maximumPoolSize參數都被設置爲創建FixedThreadPool時指 定的參數nThreads。
當線程池中的線程數大於corePoolSize時,keepAliveTime爲多餘的空閒線程等待新任務的 最長時間,超過這個時間後多餘的線程將被終止。這裏把keepAliveTime設置爲0L,意味着多餘 的空閒線程會被立即終止。
FixedThreadPool的execute()方法說明
- 當前運行的線程少於corePoolSize,則創建新線程執行任務。
- 當前運行的線程數等於corePoolSize,將任務加入LinkedBlockingQueue。
- 線程執行第1步中的任務後,會在循環中反覆從LinkedBlockingQueue獲取任務來執行。
FixedThreadPool使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列(隊列的容量爲 Integer.MAX_VALUE)。
- 當線程池中的線程數達到corePoolSize後,新任務將在無界隊列中等待。因此線程池中的線程數不會超過corePoolSize。
- 使用無界隊列時maximumPoolSize將是一個無效參數
- 使用無界隊列時keepAliveTime將是一個無效參數
- 由於使用無界隊列,運行中的FixedThreadPool不會拒絕任務(不會調用RejectedExecutionHandler.rejectedExecution方法)。
SingleThreadExecutor詳解
它是使用單個worker線程的Executor,靜態構造方法如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
corePoolSize和maximumPoolSize被設置爲1。其他參數與 FixedThreadPool相同。
SingleThreadExecutor的execute()說明
- 如果當前運行的線程數少於corePoolSize(即線程池中無運行的線程),則創建一個新線程來執行任務。
- 當前線程池中有一個運行的線程,將任務加入入LinkedBlockingQueue。
- 線程執行完第一步的任務後,會在一個無限循環中反覆從LinkedBlockingQueue獲取任務來 執行。
CachedThreadPool詳解
它是一個會根據需要創建新線程的線程池,下面是創建的靜態方法。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize被設置爲0,即corePool爲空;maximumPoolSize被設置爲 Integer.MAX_VALUE,即maximumPool是無界的;keepAliveTime設置爲60L,表示CachedThreadPool中的空閒線程 如果等待新任務超過60秒後將會被終止。
CachedThreadPool使用沒有容量的SynchronousQueue作爲線程池的工作隊列,但 CachedThreadPool的maximumPool是無界的。因此,如果主線程提交任務的速度高於 maximumPool中線程處理任務的速度時,CachedThreadPool會不斷創建新線程。極端情況下, CachedThreadPool會因爲創建過多線程而耗盡CPU和內存資源。
SynchronousQueue是一個沒有容量的阻塞隊列。每個插入操作必須等待另一個線程的對應移除操作
CachedThreadPool的execute()方法說明
- 執行SynchronousQueue.offer(Runnable task)。如果當前maximumPool中有空閒線程 正在執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那麼空閒線程成功從隊列取任務。execute()方法執行完成,否則執行下面步驟。
- 當初始maximumPool爲空,或者maximumPool中當前沒有空閒線程時,沒有線程執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。此時CachedThreadPool會創建一個新線程執行任務,execute()方法執行完成。
- 在上步中新創建的線程執行完任務後,會執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),該操作讓空閒線程最多再隊列中等待60s。
ScheduledThreadPoolExecutor詳解
該類繼承自ThreadPoolExecutor,主要用來在給定的延遲之後運行任務,或者定期執行任務。
ScheduledThreadPoolExecutor可以在構造函數中指定多個對應的後臺線程數,而Timer對應的是單個後臺線程。
ScheduledThreadPoolExecutor的運行機制
ScheduledThreadPoolExecutor使用的是支持延時獲取元素的無界阻塞隊列DelayQueue。
ScheduledThreadPoolExecutor執行主要分爲兩部分:
- 當調用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法時,會向ScheduledThreadPoolExecutor的DelayQueue添加一個實現了 RunnableScheduledFutur接口的ScheduledFutureTask。
- 線程池中的線程從DelayQueue中獲取ScheduledFutureTask,然後執行任務。
ScheduledThreadPoolExecutor的實現
ScheduledThreadPoolExecutor會把待調度的任務(ScheduledFutureTask) 放到一個DelayQueue中。
ScheduledFutureTask主要包含3個成員變量:
- long型成員變量time,表示這個任務將要被執行的具體時間。
- long型成員變量sequenceNumber,表示這個任務被添加到ScheduledThreadPoolExecutor中 的序號。
- long型成員變量period,表示任務執行的間隔週期。
DelayQueue封裝了一個PriorityQueue(優先級隊列),這個PriorityQueue會對隊列中的ScheduledFutureTask進行排序。排序時,time小的排在前面(時間早的任務將被先執行)。如果兩個 ScheduledFutureTask的time相同,就比較sequenceNumber,sequenceNumber小的排在前面(如果兩個任務的執行時間相同,那麼先提交的任務將被先執行)。
ScheduledThreadPoolExecutor的任務執行步驟
- 線程1從從DelayQueue中獲取已到期的ScheduledFutureTask(DelayQueue.take())。到期任務是指ScheduledFutureTask的time大於等於當前時間,並執行任務。
- 線程1修改ScheduledFutureTask的time變量便爲下次將要被執行的時間,並放入DelayQueue中(DelayQueue.add())。
DelayQueue.take()方法
獲取任務步驟如下:
- 獲取鎖
- 獲取週期任務,若PriorityQueue爲空,當前線程到Condition中等待;否則執行下一步
- 若PriorityQueue頭元素的time參數比當前時間大,到Condition中等待直至time時間;否則執行下一步
- 獲取PriorityQueue的頭元素,若PriorityQueue不爲空,則喚醒在Condition中等待的所有線程。
- 釋放鎖
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 1
try {
for (;;) {
E first = q.peek();
if (first == null) {
available.await(); // 2
} else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay > 0) {
long tl = available.awaitNanos(delay); // 3
} else {
E x = q.poll(); // 4
assert x != null;
if (q.size() != 0)
available.signalAll(); // 4
return x;
}
}
}
} finally {
lock.unlock(); // 6
}
}
ScheduledThreadPoolExecutor在一個循環中反覆執行步驟2,直到線程從隊列獲取到一個元素後,才退出循環。
DelayQueue.offer()方法
添加任務步驟如下:
- 獲取Lock
- 向PriorityQueue添加任務
- 若上一步添加的任務是隊列的頭元素,則喚醒在Condition中等待的所有線程
- 釋放鎖
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); // 1
try {
E first = q.peek();
q.offer(e); // 2
if (first == null || e.compareTo(first) < 0)
available.signalAll(); // 3
return true;
} finally {
lock.unlock(); // 4
}
}
FutureTask詳解
FutureTask簡介
FutureTask除了實現Future接口外,還實現了Runnable接口。因此,FutureTask可以交給 Executor執行,也可以由調用線程直接執行(FutureTask.run())。當FutureTask.run()方法被執行時,FutureTask有下面3種狀態:
- 未啓動。FutureTask.run()方法還沒有被執行之前,FutureTask處於未啓動狀態。當創建一 個FutureTask,且沒有執行FutureTask.run()方法之前,這個FutureTask處於未啓動狀態。
- 已啓動。FutureTask.run()方法被執行的過程中,FutureTask處於已啓動狀態。
- 已完成。FutureTask.run()方法執行完後正常結束,或被取消(FutureTask.cancel(…)),或 執行FutureTask.run()方法時拋出異常而異常結束,FutureTask處於已完成狀態。
get()方法和cancel()之間的影響
- 當FutureTask處於未啓動或已啓動狀態時,執行FutureTask.get()方法將導致調用線程阻塞。
- 當FutureTask處於已啓動狀態時,執行FutureTask.cancel(true)方法將以中斷執行此任務線程 的方式來試圖停止任務;
- 當FutureTask處於已啓動狀態時,執行FutureTask.cancel(false)方法將 不會對正在執行此任務的線程產生影響(讓正在執行的任務運行完成);
- 當FutureTask處於已完 成狀態時,執行FutureTask.cancel(…)方法將返回false。
FutureTask的使用
當一個線程需要等待另一個線程把某任務執行完後,他才能繼續執行,此時可以用FutureTask。
測試:假設有多個線程執行若干個任務,每個任務最多隻被執行一次,且只允許一個線程執行,其他線程需等待這個任務執行完後,才能繼續執行。這些若干任務存儲在currentHashMap
final ConcurrentHashMap<Object, Future<String>> taskCache = new ConcurrentHashMap<>();
String executionTask(final String taskName) throws Exception{
while (true) {
Future<String> future = taskCache.get(taskName); // 1.1 2.1
if(future == null) {
Callable<String> task = new Callable<String>() {
@Override
public String call() throws Exception {
return taskName;
}
};
FutureTask<String> futureTask = new FutureTask<String>(task); // 1.2
future = taskCache.putIfAbsent(taskName, future); // 1.3
if(future == null) {
futureTask.run(); //1.4
}
}
return future.get(); // 1.5
}
}
代碼執行示意圖如下。當兩個線程試圖同時執行同一任務時,假設t1執行1.3後,t2執行2.1,則t2需在2.2中等待,直到t1執行完1.4後,t2才能從FutureTask.get()返回。
FutureTask的實現
FutureTask的實現基於AbstractQueuedSynchronizer(簡稱爲AQS)。java.util.concurrent中 的很多可阻塞類(比如ReentrantLock)都是基於AQS來實現的。基於於AQS實現的同步器包括:ReentrantLock、Semaphore、ReentrantReadWriteLock、 CountDownLatch和FutureTask。
每一個基於AQS實現的同步器都包括兩種類型的操作:
- 至少一個基於acquire操作這個操作阻塞調用線程,除非/直到AQS的狀態允許這個線程繼續 執行。FutureTask的acquire操作爲get()/get(long timeout,TimeUnit unit)方法調用。
- ·至少一個release操作。這個操作改變AQS的狀態,改變後的狀態可允許一個或多個阻塞 線程被解除阻塞。FutureTask的release操作包括run()方法和cancel(…)方法。
FutureTask聲明瞭一個內部私有的繼承於AQS的子類 Sync,Sync實現了AQS的tryAcquireShared(int)方法和tryReleaseShared(int)方法,Sync通過這 兩個方法來檢查和更新同步狀態。
如上圖所示。FutureTask.get()方法會調用AQS.acquireSharedInterruptibly(int arg)方法,該方法執行過程如下:
- 調用AQS.acquireSharedInterruptibly(int arg)方法,這個方法首先會回調在子類Sync中實 現的tryAcquireShared()方法來判斷acquire操作是否可以成功。acquire操作可以成功的條件爲: state爲執行完成狀態RAN或已取消狀態CANCELLED,且runner不爲null。
- 如果成功則get()方法立即返回。如果失敗則到線程等待隊列中去等待其他線程執行 release操作。
- 當其他線程執行release操作(比如FutureTask.run()或FutureTask.cancel(…))喚醒當前線 程後,當前線程再次執行tryAcquireShared()將返回正值1,當前線程將離開線程等待隊列並喚醒它的後繼線程
- 最後返回計算的結果或拋出異常。
FutureTask.run()的執行過程
- 執行在構造函數中指定的任務(Callable.call()))。
- 以原子方式來更新同步狀態(調用AQS.compareAndSetState(int expect,int update),設置 state爲執行完成狀態RAN)。如果這個原子操作成功,就設置代表計算結果的變量result的值爲 Callable.call()的返回值,然後調用AQS.releaseShared(int arg)。
- AQS.releaseShared(int arg)首先會回調在子類Sync中實現的tryReleaseShared(arg)來執 行release操作(設置運行任務的線程runner爲null,然會返回true);AQS.releaseShared(int arg), 然後喚醒線程等待隊列中的第一個線程。
- 。當某個線程執行FutureTask.run()方法或FutureTask.cancel(...)方法時,會喚醒線程等待隊列的第一個線程