背景
Timer的優點在於簡單易用,但是由於所有的任務都是由同一個線程來調度,因此所有的任務都是串行執行的,同一時間只能有一個任務在執行,在前一個任務的延時都會影響到之後的任務。同時,Timer不會捕獲TimerTask的異常,只是簡單的停止,這樣也肯定會影響到之後任務的執行。當然可以爲了完成多線程適應多個Timer,只是這些Timer需要自己來管理,而不是一個框架體系。
鑑於Timer的上述缺陷,Java5推出了基於線程池設計的ScheduledThreadPoolExecutor。其設計思想是,每一個被調度的任務都會由線程池中的一個線程去執行,因此任務是併發的,相互之間不會有干擾。
而且,只有當任務的執行時間到來時,ScheduledThreadPoolExecutor纔會真正的啓動一個線程,其餘時間ScheduledThreadPoolExecutor都是在輪詢任務的狀態。
Java中的ScheduledThreadPoolExecutor類
我們先來看一下ScheduledThreadPoolExecutor類的具體實現源碼。
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
...
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
}
發現ScheduledThreadPoolExecutor是繼承於ThreadPoolExecutor,並且實現了ScheduledExecutorService接口,其中構造方法爲調用了父類ThreadPoolExecutor類的構造方法,只不過是阻塞隊列變爲了DelayedWorkQueue,而不是
ThreadPoolExecutor默認的LinkedBlockingQueue。關於DelayedWorkQueue,我們後面在說。
ThreadPoolExecutor不用多說,下面我們看看ScheduledExecutorService接口,源碼如下:
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
看起來和ThreadPoolExecutor類結構很類似。ScheduledExecutorService接口繼承了ExecutorService接口,該接口中主要定義了一些線程池狀態管理的一些方法。下面來看看該接口中定義的4個方法,和Timer中的方法類似,大概可以猜到
其實就是和任務調度有關的方法。其中第一個方法和第二個方法區別不大,一個傳入Runnable,一個傳入Callable,大家對
這兩個也熟悉了,一個有返回值,一個沒有而且。第三,四個方法多個一個delay參數。其實就懂了
- 第1,2個方法就是進行一次延遲調度,延遲delay這麼長時間,單位爲:TimeUnit。
- 第3個方法和Timer中的scheduleAtFixedRate類似,即按照上一次預計調度的時間進行多次調度;例如,延遲2s開始執行,5s一次,那麼就是2、7、12、17,如果中間由於某種原因導致線程不夠用,沒有得到調度的機會,那麼接下來計算的時間會優先計算進去,即7s時沒有得到調度機會,到9s纔開始執行,其實下一次的執行時間依然是12s。
- 第4個方法,其實就類型於Timer中的schedule了,如果7s的線程到9s纔開始執行,那麼下一次的執行時間爲9+5=14s,而不是12s了
下面接口看ScheduledThreadPoolExecutor中的一些方法。既然是繼承自ThreadPoolExecutor類,那麼其也是用execute()或
submit()來提交方法,下面來看看這兩個方法:
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public <T> Future<T> submit(Runnable task, T result) {
return schedule(Executors.callable(task, result), 0, NANOSECONDS);
}
這裏只列舉了兩個,其實方法內部也全都是調用的schedule方法,這下明白了,ScheduledExecutorService中定義的4個方法纔是核心,發現execute和submit的期望執行時間爲0,也就是說立即就要執行。下面來看看這個4個方法是怎麼實現的:
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
發現,不論是哪個方法裏面,都會創建一個ScheduledFutureTask類的實例,先不管這個類,我們繼續看這些方法。
new好ScheduledFutureTask,又用decorate方法包裝了下叫做RunnableScheduledFuture,也就是適配了下而已,呵呵,代碼
裏面就是一個return操作,並沒有什麼卵用,感覺java這樣做的目的是方便子類去擴展。
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
然後調用delayExecute方法,顧名思義,就是延遲執行的意思。下面看看這個方法:private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
如果線程池已經關閉了,那麼就採取任務拒絕策略;如果線程池沒有關閉,就加入到父類ThreadPoolExecutor的隊列中,這裏的隊列就是剛開始初始化時候的DelayedWorkQueue。加入後,會再次判斷線程池的狀態,定時任務是否取消等,如果是則取
消任務,否則就調用ensurePrestart(),其實該方法就是根據corePoolSize來調用addWorker()。對這塊不懂的,請參考
ThreadPoolExecutor。
下面再看看ScheduledFutureTask類:
ScheduledFutureTask
ScheduledFutureTask類是ScheduleThreadPoolExecutor的內部類,其繼承了FutureTask,並實現了RunnableScheduledFuture
,RunnableScheduledFuture間接繼承了Runnable和Delayed,所以該類中也實現了run方法作爲啓動方法,也就是說,其實就
是執行的該類(Runnable)中的run方法。該類中定義變量爲:
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
private final long sequenceNumber; // 序列號, 生成對象時賦值
private long time; // 過多少時間執行,單位爲納秒
private final long period; // 重複週期
RunnableScheduledFuture<V> outerTask = this; // 當前task
int heapIndex; // 延遲隊列的索引,方便快速取消task
}
下面來看看方法:ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
}
構造方法其實就是先調用父類的構造方法將任務賦值,然後再對time 和 period進行賦值public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
public boolean isPeriodic() {
return period != 0;
}
- getDelay方法就是根據time來計算出下一次要執行的延遲時間
- compareTo方法傳入的是Delayed類型,不難猜測出,ScheduledFutureTask和Delayed有某種繼承關係,沒錯,ScheduledFutureTask實現了Delayed的接口,只是它是間接實現的;並且Delayed接口繼承了Comparable接口;這個方法就是用來排序的,並且是根據時間來排序的,在和Timer中的實現是一致的,就是delay時間短的在隊列最前。
- isPeriodic就是判斷任務是否是週期執行的
下面來看看run方法:
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic)) // 檢查當前線程池狀態是否需要取消
cancel(false);
else if (!periodic) // 如果不是週期性任務,直接調用父類FutureTask的run方法執行任務
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) { // 否則,調用父類FutureTask的runAndReset方法,即
setNextRunTime(); // 執行任務,但不設置結果,以便計算出下次執行時間後,重複執行
reExecutePeriodic(outerTask); // 計算好下次delay時間後,重新將任務提交到隊列中
}
}
看到這裏,其實就已經很明瞭了,其實ScheduledThreadPoolExecutor就是調用父類ThreadPoolExecutor的方法,傳入的是一個ScheduledFutureTask類型的對象。那麼它是怎麼來實現調度的呢,下面我們來看看DelayedWeorkQueue。
DelayedWorkQueue
在ThreadPoolExecutor剖析中講過,在worker的runWork()方法中,是調用getTask()方法來從隊列中獲取task的。getTask方法
中你會發現,如果沒有設置超時,默認是調用workQueue.take()方法獲取數據,否則用poll來獲取數據。而添加到隊列中是用的
offer方法,下面我們便一一看一下這些方法
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
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 && queue[0] != null)
available.signal();
lock.unlock();
}
}
先來看看queue 和 available的定義:private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final Condition available = lock.newCondition();
這下清楚了,這是一個死循環,跳出條件爲,隊列如果不空,且第一個任務的等待時間小於0。隊列爲空就等待,否則就計算任
務等待時間,如果小於0,直接返回,否則等到這麼長的時間。(注意,其實這裏和Timer中的隊列一致,不過Timer中的實現方
式爲wait和notify/notifyAll),而這裏的實現方式是用的condition。
看看其調用的firstPoll方法:
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
int s = --size;
RunnableScheduledFuture<?> x = queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
setIndex(f, -1);
return f;
}
再看看offer方法:
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
grow();
size = i + 1;
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
明白了吧,這裏的DelayedWorkQueue與Timer中的TaskQueue都是以最小堆的方式來進行排序的。當新增一個元素時,調用siftUp方法,將等待時間最短的task移到最前,同時調用signal方法來喚醒可能等待的線程。而進行take或者cancel的時候,有會
調用siftDown方法。是用什麼進行比較的呢,上面有提到,ScheduledFutureTask中實現的compareTo方法。一切都聯通起來了
吧。
總結:
1.ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor,所以本質上說ScheduledThreadPoolExecutor還是一個線程池,也有coorPoolSize和workQueue,接受Runnable的子類作爲任務被多線程調,特殊的地方在於,自己實現了工作隊列,
目的是能夠按一定的順序對工作隊列中的元素進行排序,比如,按照距離下次執行時間的長短的升序方式排列工作任務,讓需要
儘快執行的任務排在隊首,“不那麼着急”的任務排在隊列後方。從而方便線程獲取執行任務。
並配合FutureTask執行但是不設置結果的API:runAndReset() 來實現任務的週期執行(每次執行完畢後不設置執行結果,而是計
算出下次執行的時間,重新放到工作隊列中,等待下次調用)。
2.調用的ScheduledFutureTask的包裝,由在ThreadPoolExecutor中的Worker調用你傳入的Runnable的run方法,變成了Worker調用Runnable的run方法,由它來處理時間片的信息調用你傳入的線程。
3.ScheduledFutureTask類在整個過程中提供了基礎參考的方法,其中最爲關鍵的就是實現了接口Comparable,實現內部的compareTo方法,也實現了Delayed接口中的getDelay方法用以判定時間
4.等待隊列由在ThreadPoolExecutor中默認使用的LinkedBlockingQueue換成了DelayQueue(它是被DelayWorkQueue包裝了一下子,沒多大區別),而DelayQueue主要提供了一個信號量“available”來作爲寫入和讀取的信號控制開關,通過另一個優先級隊列“PriorityQueue”來控制實際的隊列順序,他們的順序就是基於上面提到的ScheduledFutureTask類中的compareTo方法,而是否運行也是基於getDelay方法來實現的。
5.ScheduledFutureTask類的run方法會判定是否爲時間片信息,如果爲時間片,在執行完對應的方法後,開始計算下一次執行時間(注意判定時間片大於0,小於0,分別代表的是以當前執行完的時間爲準計算下一次時間還是以當前時間爲準),這個在前面有提到。
6.它是支持多線程的,和Timer的機制最大的區別就在於多個線程會最徵用這個隊列,隊裏的排序方式和Timer有很多相似之處,並非完全有序,而是通過位移動來儘量找到合適的位置,有點類似貪心的算法。