併發ScheduledThreadPoolExecutor源碼分析

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);
 }

如果當前任務還沒有執行完,下一次執行任務的時間到了,則不會併發執行,下次要執行的任務會延遲,要等到當前任務執行完畢後再次執行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章