ScheduledThreadPoolExecutor分析
從圖中我們可以看到ScheduledThreadPoolExecutor繼承ThreadPoolExecutor實現了ScheduledExecutorService介面。它相當於提供了"延遲"和"週期執行"功能的ThreadPoolExecutor,還有兩個重要內部類DelayedWorkQueue和ScheduledFutureTask
構造方法
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,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
複製程式碼
因為其繼承了ThreadPoolExecutor,呼叫了ThreadLocalExecutor的構造方法。當核心執行緒數達到corePoolSize,會將任務提交給有界阻塞佇列DelayedWorkQueue。ScheduledThreadPoolExecutor執行緒池最大執行緒數為Integer.MAX_VALUE
主要方法
ScheduledThreadPoolExecutor實現了ScheduledExecutorService介面,該介面提供瞭如下方法:
// 在給定延遲後,執行Runnable任務
public ScheduledFuture schedule(Runnable command,
long delay, TimeUnit unit);
// 在給定延遲後,執行Callable任務
public ScheduledFuture schedule(Callable callable,
long delay, TimeUnit unit);
// 給定延遲(initialDelay)之後,隨後以給定時間(period)為週期執行任務
// 即執行將在initialDelay之後開始,然後是initialDelay+period,
// 再是initialDelay + 2*period,依此類推
// 如果上一個任務沒有執行完畢,則需要等上一個任務執行完畢後立即執行
public ScheduledFuture scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
// 建立並執行在給定的初始延遲(initialDelay)之後首先啟用的定期操作
// 隨後每個任務執行的終止和下一個執行的開始之間給定的延遲(delay)
public ScheduledFuture scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
複製程式碼
第一、第二個schedule方法都是一次性操作只不過入參一個是Runnable,一個是callable
scheduleAtFixedRate、scheduleWithFixedDelay方法可以看如下示例
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("hh:MM:ss");
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
Runnable task1 = () -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "測試" + sdf.format(new Date()));
};
Runnable task2 = () -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "測試" + sdf.format(new Date()));
};
executorService.scheduleAtFixedRate(task1, 0, 2, TimeUnit.SECONDS);
executorService.scheduleWithFixedDelay(task2, 0, 2, TimeUnit.SECONDS);
}
輸出:
pool-1-thread-1測試11:12:37
pool-1-thread-2測試11:12:37
pool-1-thread-1測試11:12:40
pool-1-thread-2測試11:12:42
pool-1-thread-1測試11:12:43
pool-1-thread-1測試11:12:46
pool-1-thread-2測試11:12:47
複製程式碼
週期間隔2秒,任務耗時3秒
scheduleAtFixedRate方法:
1.若任務耗時超過週期間隔,則需要等待上個任務完成下個任務才能執行
2.若任務耗時小於週期間隔,則下個任務按週期間隔執行任務
scheduleWithFixedDelay方法:
1.下任務等到上個任務執行完成+週期間隔之後才執行任務
邏輯處理相差不多,以schedule方法為例分析
public ScheduledFuture schedule(Callable callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture t = decorateTask(callable,
new ScheduledFutureTask(callable,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
複製程式碼
先引數校驗,再構造task,最後呼叫delayedExecute()方法延遲執行任務
ensurePrestart主要呼叫了addWorker方法,此方法主要做了兩件事:private void delayedExecute(RunnableScheduledFuture task) { // 判斷執行緒池是否處於RUNNING狀態,不處於則根據相應拒絕策略拒絕任務 if (isShutdown()) reject(task); else { // 往阻塞佇列中新增任務 super.getQueue().add(task); if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task)) task.cancel(false); else ensurePrestart(); } }
複製程式碼void ensurePrestart() { int wc = workerCountOf(ctl.get()); if (wc < corePoolSize) addWorker(null, true); else if (wc == 0) addWorker(null, false); } 複製程式碼
複製程式碼
1.迴圈CAS將執行緒池中的執行緒數加一
2.新建一個執行緒並啟用
當執行緒執行任務,都會呼叫到任務的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);
}
}
複製程式碼
1.先判斷該任務是否可以執行,若不能執行則呼叫cancel方法取消
2.再判斷是否是週期性任務,若不是直接執行
3.最後呼叫runAndReset方法執行任務並重置,setNextRunTime方法設定任務下次的執行時間,reExecutePeriodic方法重新把任務新增到佇列中.
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
void reExecutePeriodic(RunnableScheduledFuture task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
複製程式碼
DelayedWorkQueue
ScheduledThreadPoolExecutor是把任務新增到DelayedWorkQueue中,它是一個基於堆的資料結構,通過ScheduledFutureTask的compareTo方法比較大小,小的排在前面,大的排在後面
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;
}
複製程式碼
首先按照time排序,time小的排在前面,大的排在後面,若time相同,則使用sequenceNumber排序,小的排在前面,大的排在後面