https://www.jianshu.com/p/18f4c95aca24
流程:
1 提交任務的時候,任務被包裝成ScheduledFutureTask對象加入延遲隊列並啓動一個woker線程。
2 用戶提交的任務加入延遲隊列時,會按照執行時間進行排列,也就是說隊列頭的任務是需要最早執行的。而woker線程會從延遲隊列中獲取任務,如果已經到了任務的執行時間,則開始執行。否則阻塞等待剩餘延遲時間後再嘗試獲取任務。
3 任務執行完成以後,如果該任務是一個需要週期性反覆執行的任務,則計算好下次執行的時間後會重新加入到延遲隊列中。
特性:
1 使用DelayedWorkQueue作爲阻塞隊列,並沒有像ThreadPoolExecutor類一樣開放給用戶進行自定義設置。該隊列是ScheduledThreadPoolExecutor類的核心組件,後面詳細介紹。
2 這裏沒有向用戶開放maximumPoolSize的設置,原因是DelayedWorkQueue中的元素在大於初始容量16時,會進行擴容,也就是說隊列不會裝滿,maximumPoolSize參數即使設置了也不會生效。
3 worker線程沒有回收時間,原因跟第2點一樣,因爲不會觸發回收操作。所以這裏的線程存活時間都設置爲0。
繼承了 ThreadPoolExecutor 實現了 ScheduledExecutorService 接口
scheduleAtFixedRate 方法
代碼塊
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
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
//然後在調用decorateTask進行包裝,該方法是留給用戶去擴展的,默認是個空方法
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
//延遲執行
delayedExecute(t);
return t;
}
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
//這裏是增加一個worker線程,避免提交的任務沒有worker去執行
//原因就是該類沒有像ThreadPoolExecutor一樣,woker滿了才放入隊列
ensurePrestart();
}
}
線程啓動後,由ScheduledThreadPoolExecutor的父類ThreadPoolExecutor接管。
ensurePrestart:
代碼塊
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
// 增加一個線程並啓動
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
addWorker方法 會調用新創建線程的 start方法
線程獲得cpu時間片後 執行線程的run方法 調用runworker方法
代碼塊
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask; // task爲null
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 執行getTask()方法從延遲隊列中獲得任務
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 執行run()方法
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
ScheduledFutureTask 內部類 中的run方法:
代碼塊
// 重寫run 方法 實現 到達週期後 任務入隊
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
// runAndReset執行最初設置的Runnable代碼,若代碼成功執行,則返回true,否則返回false(runnable中的代碼拋出異常)。
// 而只有當返回true時,執行reExecutePeriodic代碼 把下次的任務添加進入隊列
else if (ScheduledFutureTask.super.runAndReset()) {
// 設置下次執行時間
setNextRunTime();
// 任務重新入隊
reExecutePeriodic(outerTask);
}
}
下次任務執行時間依賴 上次任務執行耗時
任務執行中有一次 遇到異常 則線程死掉 後續也不會再往隊列中增加任務
// 任務重新入隊方法: reExecutePeriodic()
代碼塊
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
reExecutePeriodic 以及delayedExecute 均調用了super.getQueue().add(task)行代碼,
ScheduledThreadPoolExecutor類在內部自己實現了一個基於堆數據結構的延遲隊列。add方法最終會落到offer方法中
代碼塊
public boolean add(Runnable e) {
return offer(e);
}
public boolean offer(Runnable x) {
// 參數校驗
if (x == null)
throw new NullPointerException();
// 任務強轉爲RunnableScheduledFuture
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);
}
//如果新加入的元素就是隊列頭,這裏有兩種情況
//1.這是用戶提交的第一個任務
//2.新任務進行堆調整以後,排在隊列頭
if (queue[0] == e) {
//這個變量起優化作用,後面說
leader = null;
//加入元素以後,喚醒worker線程
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
runWorker()中調用的getTask() 方法:
代碼塊
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
// 循環重試獲取任務
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 校驗任務狀態
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 線程數目 超過最大線程數 或 ((超過核心線程數 或 允許核心線程超時)且已超時 且 (線程數>1 或隊列爲空) ) 執行回收線程
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
// 有超時時間的獲取隊列任務
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 無限期阻塞獲取隊列任務
workQueue.take();
if (r != null)
return r;
// 沒有拿到任務 認爲超時了
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
workQueue.take();: 延遲隊列中的take 方法
代碼塊
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 循環重試
for (;;) {
// 僅僅獲取隊頭元素 不出隊列
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
// 隊列中任務爲空 執行await() 阻塞等待 await() 會釋放鎖資源
available.await();
else {
//成功拿到任務
//計算任務執行時間,這個delay是 任務觸發時間 減去當前時間
long delay = first.getDelay(NANOSECONDS);
// 到了觸發時間,則執行出隊操作
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
//這裏表示該任務已經分配給了其他線程,當前線程等待喚醒就可以 其他線程拿到最新任務正在執行作爲leader
if (leader != null)
available.await();
else {
//否則把給任務分配給當前線程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//當前線程等待任務剩餘延遲時間
available.awaitNanos(delay);
} finally {
//這裏線程醒來以後,什麼時候leader會發生變化呢?
//就是上面的添加任務的時候
// 重置leader 重新循環拿任務
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//如果隊列不爲空,則喚醒其他woker線程
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
leader 變量優化 多個線程執行任務時的等待邏輯
這裏爲什麼會加入一個leader變量來分配阻塞隊列中的任務呢?原因是要減少不必要的時間等待。
比如說現在隊列中的第一個任務1分鐘後執行,那麼用戶提交新的任務時會不斷的加入woker線程,如果新提交的任務都排在隊列後面,
也就是說新的woker現在都會取出這第一個任務進行執行延遲時間的等待,當該任務到觸發時間時,會喚醒很多woker線程,這顯然是沒有必要的。