ScheduledThreadPoolExecutor是一個可以在指定一定延時時間後或者定時進行任務調度的線程池,ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor並實現了ScheduledExecutorService接口。線程池的隊列是DelayedWorkQueue(他是ScheduledThreadPoolExecutor的一個內部類)。
還要在看一下ScheduledFutureTask(同樣是ScheduledThreadPoolExecutor的一個內部類),繼承FutureTask,FutureTask的內部有一個變量state用來表示任務的狀態,一開始狀態爲NEW。下面是所有狀態定義。
private volatile int state;
private static final int NEW = 0;//初始化狀態
private static final int COMPLETING = 1;//執行中狀態
private static final int NORMAL = 2;//正常運行結束狀態
private static final int EXCEPTIONAL = 3;//運行中異常
private static final int CANCELLED = 4;//任務被取消
private static final int INTERRUPTING = 5;//任務正在被中段
private static final int INTERRUPTED = 6;//任務已經被中斷
ScheduledFutureTask內部還有一個變量period用來表示任務的類型,如果==0,則表示任務是一次性的,任務執行完畢後退出,如果爲負數,說明當前任務是以固定延時的定時可重複執行任務,如果爲正數,說明任務是以固定頻率的定時定時可重複執行任務。
一、chedule(Runnable command, long delay, TimeUnit unit)方法解析
他的作用是提交一個延時執行的任務,任務從提交時間算起延時單位爲unit的delay時間後開始執行,任務只會執行一次。
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;
}
首先是參數判斷,爲空則拋出異常,接着是裝飾任務,把提交的command(Runnable對象)轉換爲ScheduledFutureTask,ScheduledFutureTask是具體放入延時隊列裏面的東西,由於是延時任務,所以ScheduledFutureTask實現了getDelay和compareTo方法,triggerTime方法將延時時間轉換爲絕對時間,也就是把當前時間的納秒加上延遲的納秒後的值。
ScheduledFutureTask的構造如下,設period爲0,表示該任務是一次性任務。
ScheduledFutureTask(Callable<V> callable, long ns) {
super(callable);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
然後通過delayedExecute將任務添加到延時隊列。
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();
}
}
上述代碼首先確保線程池沒關閉,關閉則執行拒絕策略,沒關閉將任務添加到延時隊列,添加後再重新檢查線程池是否關閉,如果關閉則從延時隊列裏面刪除剛纔添加的任務。
再看ensurePrestart方法。
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
//增加核心線程數
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
首先獲取到了線程池中的線程數,如果個數小於核心線程池則新增一個線程,否則如果當前線程數爲0個,則同樣新增一個線程。
我們知道ThreadPoolExecutor在具體執行任務的線程是Worker線程,Worker線程調用具體任務的run方法來執行,在這裏的任務是ScheduledFutureTask,所以來看看ScheduledFutureTask的run方法。
public void run() {
//是否執行一次
boolean periodic = isPeriodic();
//取消任務
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
//定時任務
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
首先判斷任務是一次性的還是可重複執行的任務,ScheduledFutureTask在構造方法中已經設置了period爲0,所以,這裏會返回false。
public boolean isPeriodic() {
return period != 0;
}
然後判斷當前任務是否應該被取消。爲true則取消任務。
由於periodic爲false,則會執行代碼ScheduledFutureTask.super.run();
,調用父類FutureTask的run方法。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
FutureTask.run()首先判斷任務狀態,如果不是NEW則直接返回,或者如果任務狀態爲NEW,但是使用CAS設置當前任務的持有者爲當前線程失敗則直接返回。
然後具體調用callable的call方法執行任務,如果任務執行成功則修改任務狀態,也就是set方法。
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
使用CAS將當前任務的狀態從NEW轉換的COMPLETING。這裏當有多個線程調用時只有一個線程會成功,成功的線程在通過 UNSAFE.putOrderedInt設置任務的狀態爲正常結束狀態。
還有在任務執行失敗後,執行setException方法,和set方法類似了。
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
二、scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit)方法解析
他的作用是,當任務執行完畢後,讓其延遲固定時間後再次運行,initialDelay表示提交任務後延遲多少時間開始執行任務command,delay表示當任務執行完畢後延長多少時間後再次運行command,unit是時間單位。
這個任務會一直重複運行下去,直到任務中拋出異常、被取消、線程池關閉。
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;
}
首先也是參數判斷,爲空則拋出異常,然後將command任務轉換爲ScheduledFutureTask,然後添加延遲到隊列。
將任務添加到隊列後線程池線程會從隊列中獲取任務,然後調用ScheduledFutureTask的run方法,由於這裏period<0,所以isPeriodic返回true,則會執行方法runAndReset()。
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call();
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
runner = null;
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}
他在任務執行完畢後不會設置任務的狀態,是爲了讓任務可重複執行,看最後一句,判斷如果當前任務正常執行完畢並且任務狀態爲NEW則返回true,如果返回true則執行方法setNextRunTime(),用於設置任務下一次的執行時間。
這裏p是<0的,然後設置timer爲當前時間加上-p,也就是延遲-p時間後再次執行。
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
三、scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit)方法解析
該方法相對起始時間點以固定頻率調用指定任務,當把任務提交到線程池並延遲initialDelay時間後開始執行任務command,然後從initialDelay+period時間點再次執行,而後在initialDelay+2*period時間點再次執行,直到拋出異常或者取消、關閉線程池。
原理和scheduleWithFixedDelay類似,我們就看幾個不同點,
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;
}
首先是period=period,不再是-period。所以當前任務執行完畢後調用setNextRunTime設置任務下次執行的時間是 time += p。
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
如果當前任務還沒有執行完,下一次執行任務的時間到了,則不會併發執行,下次要執行的任務會延遲,要等到當前任務執行完畢後再次執行