線程池-執行機制ScheduledThreadPoolExecutor


1.官方文檔

A ThreadPoolExecutor that can additionally schedule commands to 
run after a given delay, or to execute periodically. This class is 
preferable to Timer when multiple worker threads are needed, or when 
the additional flexibility or capabilities of ThreadPoolExecutor (which 
this class extends) are required.

Delayed tasks execute no sooner than they are enabled, but without 
any real-time guarantees about when, after they are enabled, they will 
commence. Tasks scheduled for exactly the same execution time are 
enabled in first-in-first-out (FIFO) order of submission.

When a submitted task is cancelled before it is run, execution is 
suppressed. By default, such a cancelled task is not automatically
 removed from the work queue until its delay elapses. While this 
enables further inspection and monitoring, it may also cause 
unbounded retention of cancelled tasks. To avoid this, set 
setRemoveOnCancelPolicy(boolean) to true, which causes tasks to 
be immediately removed from the work queue at time of cancellation.

Successive executions of a task scheduled via scheduleAtFixedRate 
or scheduleWithFixedDelay do not overlap. While different executions 
may be performed by different threads, the effects of prior executions 
happen-before those of subsequent ones.

While this class inherits from ThreadPoolExecutor, a few of the 
inherited tuning methods are not useful for it. In particular, because it 
acts as a fixed-sized pool using corePoolSize threads and an 
unbounded queue, adjustments to maximumPoolSize have no useful 
effect. Additionally, it is almost never a good idea to set corePoolSize 
to zero or use allowCoreThreadTimeOut because this may leave the 
pool without threads to handle tasks once they become eligible to run.

Extension notes: This class overrides the execute and submit 
methods to generate internal ScheduledFuture objects to control per-
task delays and scheduling. To preserve functionality, any further 
overrides of these methods in subclasses must invoke superclass 
versions, which effectively disables additional task customization. 
However, this class provides alternative protected extension method 
decorateTask (one version each for Runnable and Callable) that can 
be used to customize the concrete task types used to execute 
commands entered via execute, submit, schedule, 
scheduleAtFixedRate, and scheduleWithFixedDelay. By default, a 
ScheduledThreadPoolExecutor uses a task type extending 
FutureTask. However, this may be modified or replaced using 
subclasses of the form

可在給定延遲後執行任務,或定期執行。當需要多線程,或需要線程池額外的靈活性或功能性時,ScheduledThreadPoolExecutor優於Timer。

延遲任務的執行沒有實時保證。

在提交的任務在運行之前取消時,將禁止執行。默認情況下,此類已取消的任務不會自動從工作隊列中刪除,直到其延遲結束。可以進行進一步檢查和監控,但也可能導致取消任務的無限制滯留。要避免這種情況,請將setRemoveOnCancelPolicy(boolean)設置爲true,這會使得在取消時立即從工作隊列中刪除任務。

通過scheduleAtFixedRate或scheduleWithFixedDelay任務調度來進行連續任務執行,不會導致任務執行重疊。雖然可能不同執行由不同的線程進行,但是先前執行happen-before後續的執行。

雖然這個類繼承自ThreadPoolExecutor,但是一些繼承的調整方法對它沒用。特別是,因爲使用corePoolSize線程和無界隊列,所以對maximumPoolSize的調整沒有任何有用的效果。此外,將corePoolSize設置爲零或使用allowCoreThreadTimeOut不是一個好主意,因爲會使線程池沒有線程來處理到期的定時任務。

擴展註釋:此類重寫exe​​cute和submit方法生成內部ScheduledFuture對象,以控制每個任務的延遲和調度。爲了保留功能,子類中這些方法的任何進一步重寫必須調用超類版本。但是,此類提供了替代的保護擴展方法decorateTask(Runnable和Callable各一個版本),可用於自定義由execute、submit、schedule、scheduleAtFixedRate和scheduleWithFixedDelay提交的具體任務類型。默認情況下,ScheduledThreadPoolExecutor使用擴展FutureTask的任務類型。但是,可以使用以下形式的子類來修改或替換它:

 public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {

   static class CustomTask<V> implements RunnableScheduledFuture<V> { ... }

   protected <V> RunnableScheduledFuture<V> decorateTask(
                Runnable r, RunnableScheduledFuture<V> task) {
       return new CustomTask<V>(r, task);
   }

   protected <V> RunnableScheduledFuture<V> decorateTask(
                Callable<V> c, RunnableScheduledFuture<V> task) {
       return new CustomTask<V>(c, task);
   }
   // ... add constructors, etc.
 }
This class specializes ThreadPoolExecutor implementation by

1. Using a custom task type ScheduledFutureTask, even for tasks
   that don't require scheduling because they are submitted
   using ExecutorService rather than ScheduledExecutorService
   methods, which are treated as tasks with a delay of zero.

2. Using a custom queue (DelayedWorkQueue), a variant of
   unbounded DelayQueue. The lack of capacity constraint and
   the fact that corePoolSize and maximumPoolSize are
   effectively identical simplifies some execution mechanics
   (see delayedExecute) compared to ThreadPoolExecutor.

3. Supporting optional run-after-shutdown parameters, which
   leads to overrides of shutdown methods to remove and cancel
   tasks that should NOT be run after shutdown, as well as
   different recheck logic when task (re)submission overlaps
   with a shutdown.

4. Task decoration methods to allow interception and
   instrumentation, which are needed because subclasses cannot
   otherwise override submit methods to get this effect. These
   don't have any impact on pool control logic though.
  • 1.使用自定義任務類型ScheduledFutureTask,即使對於不需要調度的任務,因爲它們是使用ExecutorService而不是ScheduledExecutorService方法提交的,這些方法被視爲延遲爲零的任務。

  • 2.使用自定義隊列(DelayedWorkQueue),無界DelayQueue的變體。與ThreadPoolExecutor相比,缺少容量約束以及corePoolSize和maximumPoolSize實際上相同的事實簡化了一些執行機制(請參閱delayedExecute)。

  • 3.支持可選的run-after-shutdown參數,這會導致關閉方法的覆蓋,以刪除和取消關閉後不應運行的任務,以及任務(重新)提交與關閉重疊時的不同重新檢查邏輯。

  • 4.允許攔截和檢測的任務裝飾方法,因爲子類不能覆蓋submit方法。但這些對池控制邏輯沒有任何影響。

2.構造器

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

使用的是自定義的DelayedWorkQueue。

3.提交任務

3.1 schedule一次性任務

    public void execute(Runnable command) {
        schedule(command, 0, NANOSECONDS);
    }
 public Future<?> submit(Runnable task) {
        return schedule(task, 0, NANOSECONDS);
    }

    public <T> Future<T> submit(Runnable task, T result) {
        return schedule(Executors.callable(task, result), 0, NANOSECONDS);
    }

    public <T> Future<T> submit(Callable<T> task) {
        return schedule(task, 0, NANOSECONDS);
    }

核心是:

    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;
    }
    /**
     * Main execution method for delayed or periodic tasks.  If pool
     * is shut down, rejects the task. Otherwise adds task to queue
     * and starts a thread, if necessary, to run it.  (We cannot
     * prestart the thread to run the task because the task (probably)
     * shouldn't be run yet.)  If the pool is shut down while the task
     * is being added, cancel and remove it if required by state and
     * run-after-shutdown parameters.
     *
     * @param task the task
     */
    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();
        }
    }
  • step1.如果線程池已關閉,則拒絕任務
  • step2.將任務加入到隊列中。不能預啓動線程,因爲任務可能還沒有到達運行的時間。
  • step3.如果任務加入後,線程池已經關閉,且狀態和run-after-shutdown參數不允許運行,則刪除並取消任務
    *step4.ensurePrestart(),正常情況下,確保有線程可以執行任務

executeExistingDelayedTasksAfterShutdown如果爲false(默認爲true),則應該在關閉後取消非週期性任務。

    void ensurePrestart() {
        int wc = workerCountOf(ctl.get());
        if (wc < corePoolSize)
            addWorker(null, true);
        else if (wc == 0)
            addWorker(null, false);
    }

3.2 scheduleAtFixedRate和scheduleWithFixedDelay重複執行的任務

scheduleAtFixedRate和scheduleWithFixedDelay任務調度的核心與 schedule一樣,都是通過delayedExecute(t)進行調度。不同點在於ScheduledFutureTask構造的不同。

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

4.run-after-shutdown參數

    /**
     * Returns true if can run a task given current run state
     * and run-after-shutdown parameters.
     *
     * @param periodic true if this task periodic, false if delayed
     */
    boolean canRunInCurrentRunState(boolean periodic) {
        return isRunningOrShutdown(periodic ?
                                   continueExistingPeriodicTasksAfterShutdown :
                                   executeExistingDelayedTasksAfterShutdown);
    }

    /**
     * False if should cancel/suppress periodic tasks on shutdown.
     */
    private volatile boolean continueExistingPeriodicTasksAfterShutdown;

    /**
     * False if should cancel non-periodic tasks on shutdown.
     */
    private volatile boolean executeExistingDelayedTasksAfterShutdown = true;

    public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) {
        continueExistingPeriodicTasksAfterShutdown = value;
        if (!value && isShutdown())
            onShutdown();
    }

    public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) {
        executeExistingDelayedTasksAfterShutdown = value;
        if (!value && isShutdown())
            onShutdown();
    }

continueExistingPeriodicTasksAfterShutdown如果爲false(默認爲false),則應該在關閉後取消或者禁止週期性任務。

5.ScheduledFutureTask


5.1 域

        /** Sequence number to break ties FIFO */
        private final long sequenceNumber;

        /** The time the task is enabled to execute in nanoTime units */
        private long time;

        /**
         * Period in nanoseconds for repeating tasks.  A positive
         * value indicates fixed-rate execution.  A negative value
         * indicates fixed-delay execution.  A value of 0 indicates a
         * non-repeating task.
         */
        private final long period;

        /** The actual task to be re-enqueued by reExecutePeriodic */
        RunnableScheduledFuture<V> outerTask = this;

        /**
         * Index into delay queue, to support faster cancellation.
         */
        int heapIndex;
  • sequenceNumber是用於打破平局的序列號。
  • time任務執行的時間
  • period重複執行任務的週期,正值表示以固定的速率執行,負值表示固定延遲執行,0表示非重複執行的任務。
  • outerTask表示在reExecutePeriodic中重新入隊的實際任務。
  • heapIndex延遲隊列的索引,支持更快的取消。

5.2 構造器

接下來看看區分schedule、scheduleAtFixedRate和scheduleWithFixedDelay的關鍵

new ScheduledFutureTask<V>(callable, triggerTime(delay, unit))

schedule調用的是如下構造器,設置period爲0:

        /**
         * Creates a one-shot action with given nanoTime-based trigger time.
         */
        ScheduledFutureTask(Callable<V> callable, long ns) {
            super(callable);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }
new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period))

new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(-delay))

調用的是如下構造器:

        /**
         * Creates a periodic action with given nano time and period.
         */
        ScheduledFutureTask(Runnable r, V result, long ns, long period) {
            super(r, result);
            this.time = ns;
            this.period = period;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

5.3 compareTo方法使用sequenceNumber打破time相同的平局

        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,然後比較sequenceNumber,用來打破time相等時的平局。

5.4 setNextRuntime

setNextRuntime是scheduleAtFixedRate和scheduleWithFixedDelay的關鍵區別

        /**
         * Sets the next time to run for a periodic task.
         */
        private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }

    long triggerTime(long delay) {
        return now() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }
  • p > 0 表示以固定速率運行任務,因此time設置爲上次任務啓動運行時間向後延遲p
  • p < 0 表示以固定的延遲運行任務,time設置爲當前任務完成時間now()向後延遲delay

5.5 heapIndex會加速cancel

        public boolean cancel(boolean mayInterruptIfRunning) {
            boolean cancelled = super.cancel(mayInterruptIfRunning);
            if (cancelled && removeOnCancel && heapIndex >= 0)
                remove(this);
            return cancelled;
        }

heapIndex會加速刪除任務在remove(this)中的刪除。

    public boolean remove(Runnable task) {
        boolean removed = workQueue.remove(task);
        tryTerminate(); // In case SHUTDOWN and now empty
        return removed;
    }

調用DelayedWorkQueue中的remove(task):

        public boolean remove(Object x) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                int i = indexOf(x);
                if (i < 0)
                    return false;

                setIndex(queue[i], -1);
                int s = --size;
                RunnableScheduledFuture<?> replacement = queue[s];
                queue[s] = null;
                if (s != i) {
                    siftDown(i, replacement);
                    if (queue[i] == replacement)
                        siftUp(i, replacement);
                }
                return true;
            } finally {
                lock.unlock();
            }
        }
        private int indexOf(Object x) {
            if (x != null) {
                if (x instanceof ScheduledFutureTask) {
                    int i = ((ScheduledFutureTask) x).heapIndex;
                    // Sanity check; x could conceivably be a
                    // ScheduledFutureTask from some other pool.
                    if (i >= 0 && i < size && queue[i] == x)
                        return i;
                } else {
                    for (int i = 0; i < size; i++)
                        if (x.equals(queue[i]))
                            return i;
                }
            }
            return -1;
        }

5.6 核心的run

        /**
         * Overrides FutureTask version so as to reset/requeue if periodic.
         */
        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);
            }
        }
    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(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        return ran && s == NEW;
    }
    /**
     * Requeues a periodic task unless current run state precludes it.
     * Same idea as delayedExecute except drops task rather than rejecting.
     *
     * @param task the task
     */
    void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }
  • 對於一次性任務,調用FutureTask.run執行
  • 對於週期性任務
    step1.首先調用FutureTask.runAndReset執行任務並重置future爲初始化狀態(與run相比,未調用set(result),因此所有狀態都是初始狀態);
    step2.然後調用setNextRunTime()重置下次任務運行的時間time;
    step3.最後將週期性任務重新入隊。

6.DelayedWorkQueue


定製延遲隊列,爲了與TPE聲明一致,該類必須聲明爲一個BlockingQueue<Runnable>,儘管其只能存放RunnableScheduledFutures。

A DelayedWorkQueue is based on a heap-based data structure
like those in DelayQueue and PriorityQueue, except that
every ScheduledFutureTask also records its index into the
heap array. This eliminates the need to find a task upon
cancellation, greatly speeding up removal (down from O(n)
to O(log n)), and reducing garbage retention that would
otherwise occur by waiting for the element to rise to top
before clearing. But because the queue may also hold
RunnableScheduledFutures that are not ScheduledFutureTasks,
we are not guaranteed to have such indices available, in
which case we fall back to linear search. (We expect that
most tasks will not be decorated, and that the faster cases
will be much more common.)

All heap operations must record index changes -- mainly
within siftUp and siftDown. Upon removal, a task's
heapIndex is set to -1. Note that ScheduledFutureTasks can
appear at most once in the queue (this need not be true for
other kinds of tasks or work queues), so are uniquely
identified by heapIndex.

DelayedWorkQueue是基於堆的數據結構,如同DelayQueue和PriorityQueue一樣。ScheduledFutureTask會記錄其在堆數組中索引,這會消除在取消時查找任務的操作,大大加快了移除操作(從O(n)到O(lgn)),並減少了垃圾滯留,否則需要等到垃圾上升到堆頂纔會被清除。但是因爲隊列也可能包含不是ScheduledFutureTasks的RunnableScheduledFutures,所以我們不能保證有這樣的索引可用,在這種情況下我們會回到線性搜索。

所有堆操作都必須記錄索引更改 - 主要在siftUp和siftDown中。刪除後,任務的heapIndex設置爲-1。請注意,ScheduledFutureTasks最多可以出現在隊列中一次(對於其他類型的任務或工作隊列,不一定是這樣),因此由heapIndex唯一標識。

6.1 入隊和出隊

入隊:

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

在ThreadPoolExecutor.Worker.run.runWorker()中:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();

getTask會從隊列中取任務:

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
        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();
            }
        }

6.2 Leader-Follower模式

參考併發容器BlockingQueue - DelayQueue及Leader-Follower模式
leader是等待隊列頭部元素的指定線程。Leader-Follower模式的這種變體用於最小化不必要的定時等待。

  • 當一個線程稱爲leader時,其會定時等待下一個delay元素過期,但是其他線程會無限期等待。
  • 當從take/poll返回之前,leader線程必須signal其他等待線程,除非在此期間有線程稱爲了新的leader。
  • 每當隊列頭部元素被更早到期的元素替換時,leader被置爲null,offer裏面q.peek() == e時,會將leader=null,此時當然會signal,重新競選leader。所以定時等待線程必須要處理失去leader時情況。

7.總結

  • 首先就是線程池本身:執行任務execute及submit被覆蓋了以實現週期任務,增加了run-after-shutdown參數來處理線程池關閉後怎麼處理週期任務
  • 線程還是沿用Worker,本身實現了AQS,在執行任務加鎖,屏蔽了中斷
  • 阻塞隊列使用的是定製的DelayedWorkQueue,優先隊列,ScheduledFutureTask會記錄其在堆數組中索引,這會消除在取消時查找任務的操作,大大加快了移除操作。但是在siftUp和siftDown中會增加維護索引的額外操作。
  • 任務是繼承自FutureTask的ScheduledFutureTask,實現了compareTo(基於time和序列號),方便放入DelayedWorkQueue。通過period區分是一次性任務還是週期性任務。通過setNextRuntime區分是scheduleAtFixedRate還是scheduleWithFixedDelay。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章