Executor框架
1. 簡介
1.1 Executor框架的兩級調度模型
在HotSpot JVM
的線程模型中,Java
線程被一對一映射爲本地操作系統線程。
Java
線程啓動時會創建一個本地操作系統線程;當該Java
線程終止時,這個操作系統線程也會被回收。
在上層,Java
多線程程序通常把應用分解爲若干任務,然後由用戶級的調度器(Executor
)將這些任務映射爲固定數量的線程;
在底層,操作系統內核將這些線程映射到硬件處理器上。
1.2 Executor框架的結構與成員
1.2.1 Executor 框架的結構
Executor
框架主要由3大部分組成:
- 任務。包括被執行任務需要實現的接口:
Runnable
接口或Callable
接口 - 任務的執行。包括任務執行機制的核心接口
Executor
,以及繼承自Executor
的ExecutorService
接口。 - 異步計算的結果。包括接口
Future
和實現Future
接口的FutureTask
類。
Executor
是一個接口,是Executor
框架的基礎,它將任務的提交與任務的執行分離開來。ThreadPoolExecutor
是線程池的核心實現類,用來執行被提交的任務。ScheduleThreadPoolExecutor
是一個實現類,可以在給定的延遲後運行命令,或者定期執行命令。Future
接口和實現Future
接口的FutureTask
類,代表異步計算的結果。Runnable
接口和Callable
接口的實現類,都可以被ThreadPoolExecutor
或ScheduleThreadPoolExecutor
執行。
1.2.2 Executor 框架的成員
(1)ThreadPoolExecutor
ThreadPoolExecutor
通常使用工廠類Executors
來創建。
Executors
可以創建3
種類型的ThreadPoolExecutor
:
SingleThreadExecutor
、FixedThreadPool
和CachedThreadPool
。
1. SingleThreadExecutor
:創建使用單個線程的SingleThreadExecutor
的API
:
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
適用於需要保證順序地執行各個任務;並且在任意時間點,不會有多個線程是活動的應用場景。
2.FixedThreadPool
:創建使用固定線程數的 FixedThreadPool
的API
。
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
適用於爲了滿足資源管理的需求,需要限制當前線程數量的應用場景,適用於負載比較重的服務器。
3.CachedThreadPool
:創建一個會根據需要創建新線程的CachedThreadPool
的API
。
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
是大小無界的線程池,適用於執行很多的短期異步任務的程序或負載較輕的服務器。
(2)ScheduledThreadPoolExecutor
1.ScheduleThreadPoolExecutor
:包含若干個線程的ScheduledThreadPoolExecutor
2.SingleThreadScheduleExecutor
:只包含一個線程的ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
適用於需要多個後臺線程執行週期任務,同時爲了滿足資源管理需求而需要限制後臺線程的數量的應用場景。
SingleThreadScheduleExecutor
適用於需要單個後臺線程執行週期任務,同時需要保證順序地執行各個任務地應用場景。
(3)Future 接口
Future
接口和實現Future
接口的FutureTask
類用來表示異步計算地結果。
(4)Runnable接口和Callable接口
可以使用工廠類Executors
把一個Runnable
包裝成一個Callable
。
public static Callable<Object> callable(Runnable task)
public static <T> Callable<T> callable(Runnable task, T result)
2. ThreadPoolExecutor 詳解
它是線程池的實現類。
public class ThreadPoolExecutor extends AbstractExecutorService {
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private final BlockingQueue<Runnable> workQueue;
private volatile RejectedExecutionHandler handler;
// ......
}
2.1 SingleThreadExecutor 詳解
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到,SingleThreadExecutor
正是由於參數corePoolSize
及maximumPoolSize
的值爲1,所以該線程池纔始終只有一個線程。
需要注意的是LinkedBlockingQueue
並不是真的沒有長度限制,其最大長度爲2^31-1
2.2 FixedThreadPool 詳解
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
keepAliveTime
設置爲0L
,意味着多餘的空閒線程會被立即終止。
FixedThreadPool
使用無界隊列作爲工作隊列,有如下影響:
1)當線程池中的線程數達到 corePoolSize
後,新任務將在無界隊列中等待,因此線程池中的線程數不會超過corePoolSize
。
2)使用無界隊列,maxnumPoolSize
無效。
3)由於 1)和 2),keepAliveSize
無效。
4)不會拒絕任務。
2.3 CachedThreadPool 詳解
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
SynchronousQueue
是一個沒有容量的阻塞隊列。每個插入操作必須等待另一個線程的對應移除操作,反之亦然。
3. ScheduleThreadPoolExecutor 詳解
主要用來在給定的延遲之後運行任務,或者定期執行任務。
3.1 運行機制
ScheduleThreadPoolExecutor
爲了實現週期性的執行任務,對ThreadPoolExecutor
做了如下的修改:
- 使用DelayQueue作爲任務隊列
- 獲取任務的方式不同
- 執行週期任務後,增加了的處理
3.2 實現原理
ScheduledThreadPoolExecutor
把待調度的任務(ScheduledFutureTask
)放到一個DelayQueue中.
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
// 表示這個任務被添加到 ScheduledThreadPoolExecutor 中的序號
private final long sequenceNumber;
// 表示這個任務將要被執行的具體時間
private long time;
// 表示任務執行的間隔週期
private final long period;
// ......
}
DelayQueue
封裝了一個 PriorityQueue
,這個PriorityQueue
會對隊列中的ScheduledFutureTask
進行排序。排序是,time
小的排在前面(時間早的任務將被先執行)。如果兩個ScheduledFutureTask
的time
相同,就比較 sequenceNumber
,sequenceNumber
小的排在前面(任務執行時間相同,先提交的任務先執行)。
1)線程1從 DelayQueue
中獲取已到期的 ScheduledFutureTask
(DelayQueue.take()
)。到期任務指 ScheduledFutureTask
的time
大於等於當前時間。
2)線程1 執行這個ScheduleFutureTask
。
3)線程1修改ScheduledFutureTask
的time
變量爲下次將要執行的時間。
4)線程1 把這個修改time
之後的ScheduledFutureTask
放回 DelayQueue
中。
獲取任務的過程:
public E take() throws InterruptedException {
// 獲取鎖
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null)
//說明隊列爲空調用condition.await()方法,會使得當前線程釋放lock然後加入到等待隊列中
available.await();
else {
// 如果第一個數據不爲空
// 獲取消息體的延遲時間(getDelay() 會在消息體內重寫 自定義添加延遲時間)
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
//如果延遲時間 小於等於0 說明已經達到了延遲時間 調用poll方法返回消息體
return q.poll();
//如果延遲時間不爲空的話 說明還需要等待一段時間 此時重新循環 所以將frist置爲空
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
// 喚醒睡眠的線程
available.signal();
lock.unlock();
}
}
1)獲取Lock
2)獲取週期任務
1、如果 PriorityQueue
爲空,當前線程到 Condition
中等待;
2、如果 PriorityQueue
的頭元素的time
時間比當前時間早,到Condition
中等待到time時間。否則返回任務。
3、獲取 PriorityQueue
的頭元素,如果PriorityQueue 不爲空,則喚醒在 Condition 中等待的所有線程
3)釋放鎖
添加任務到隊列:
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
// 獲取鎖
lock.lock();
try {
// 向 PriorityQueue 添加任務
q.offer(e);
if (q.peek() == e) {
leader = null;
// 如果添加的元素是頭元素,則喚醒在 Condition 中等待的所有線程
available.signal();
}
return true;
} finally {
// 釋放鎖
lock.unlock();
}
}
4. FutureTask 詳解
Future
接口和實現 Future
接口的FutureTask
類,代表異步計算的結果。
4.1 簡介
除了實現Future
接口外,還實現了Runnable
接口。因此FutureTask
可以交給Executore
執行,也可以調用線程直接執行(FutureTask.run()
)。根據 FutureTask.run()
方法被執行的時機,FutureTask
可以由如下3種狀態:
當FutureTask
處於未啓動狀態時,執行FutureTask.cancel()
方法,將導致調用線程阻塞。當FutureTask
處於已完成狀態時,執行FutureTask.get()
方法將導致調用線程立即返回結果或拋出異常。
當FutureTask
處於未啓動狀態時,執行 FutureTask.cancel()
方法將導致此任務用於不會被執行。當FutureTask
處於已啓動狀態時,執行 FutureTask.cancel(true)
方法將以中斷執行此任務線程的方式試圖停止任務;當FutureTask
處於已啓動狀態時,執行FutureTask.cancel(false)
方法將不會對正在運行的線程產生影響(讓正在執行的任務運行完成);當FutureTask
處於已完成狀態時,執行FutureTask.cancel(..)
方法將返回false
。
4.2 實現原理
FutureTask
的實現基於 AbstractQueuedSynchronizer(AQS)
。AQS
是一個同步框架,它提供通用機制來原子性管理同步狀態、阻塞和喚醒線程,以及被阻塞線程的隊列。
每一個基於 AQS
實現的同步器都會包含兩種類型的操作:
1、至少一個acquire
操作。這個操作阻塞調用線程,除非直到 AQS
的狀態運行這個線程繼續執行。FutureTask
的 acquire
操作爲get()/get(long timeout,TimeUnit unit)
方法調用。
2、至少一個 release
操作。這個操作改變AQS
的狀態,改變後的狀態可運行一個或多個阻塞線程被解除阻塞。FutureTask
的 release
操作包括run()
方法和cancel()
方法。
Sync
是 FutureTask
的內部私有類,它繼承自AQS
。創建FutureTask
時會創建內部私有的成員對象Sync
,FutureTask
所有的公有方法直接委託給了內部私有的Sync
。
FutureTask.get()
方法會調用 AQS.acquireSharedInterruptibly(int arg)
方法,執行過程如下:
1)調用 AQS.acquireSharedInterruptibly(int arg)
方法,這個方法首先會回調在子類Sync
中實現的tryAcquireShared()
方法來判斷 acquire
操作是否可以成功。成功的條件是:state
爲執行完成狀態RAN
或已取消狀態CANCELLED
,且runner
不爲null
。
2)如果成功則 get ()
方法立即返回。如果失敗,則到線程等待隊列中去等待其他線程執行release 操
作。
3)當其他線程執行 release
操作(如 FutureTask.run()
或FutureTask.cancel(...)
喚醒當前線程後,當前線程再次執行tryAcquireShared()
將返回值1
,當前線程將離開線程等待隊列並喚醒它的後繼線程(產生級聯喚醒)。
4)最後返回的計算結果或者拋出異常。
FutureTask.run()
的執行過程如下:
1)執行在構造函數中指定的任務(Callable.call()
)
2)以原子的方式更新同步狀態(調用AQS.compareAndState(int expect,int update)
),設置state
爲執行完成RAN
)。如果這個原子操作成功,就設置代表計算結果的變量result
的值爲Callable.call()
的返回值,然後調用AQS.releaseShared(int arg)
。
3)AQS.releaseShared(int arg)
首先會回調在子類Sync
中實現的tryReleaseShared(arg)
來執行release
操作(設置運行任務的線程 runner
爲null
,然後返回true
);AQS.releaseShared(int arg)
,然後喚醒線程等待隊列中的第一個線程。
4)調用 FutureTask.done()
當執行 Future.get()
方法時,如果 FutureTask
不是處於執行完成狀態RAN
或已取消狀態CANCELLED
,當前執行線程將到AQS
的線程等待(如下圖的線程A、B、C、D)。當線程執行FutureTask.run()
方法或FutureTask.cancel()
方法時,會喚醒線程等待隊列的第一個線程(線程E喚醒線程A)
假設開始時FutureTask
處於未啓動狀態或已啓動狀態,等待隊列中已經有3
個線程(A、B和C)在等待。此時,線程D執行get()
方法將導致線程D也到等待隊列中取等待。
當線程E執行run()
方法時,會喚醒隊列中的第一個線程A。線程A喚醒後,首先把自己從等待隊列中刪除,然後喚醒它的後繼線程B,最後線程A從get()
方法返回。線程B、C和D重複A線程的處理流程。最終,在隊列中等待的所有線程都被級聯喚醒並從get()
方法返回。