Java線程池原理分析ScheduledThreadPoolExecutor篇

前言

在上一篇線程池的文章《Java線程池原理分析ThreadPoolExecutor篇》中從ThreadPoolExecutor源碼分析了其運行機制。限于篇幅,留下了ScheduledThreadPoolExecutor未做分析,因此本文繼續從源代碼出發分析ScheduledThreadPoolExecutor的內部原理。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor同ThreadPoolExecutor一樣也可以從 Executors線程池工廠創建,所不同的是它具有定時執行,以周期或間隔循環執行任務等功能。

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {

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

}

public interface ScheduledExecutorService extends ExecutorService {
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor,因此它具有ThreadPoolExecutor的所有能力。
通過super方法的參數可知,核心線程的數量即傳入的參數,而線程池的線程數為Integer.MAX_VALUE,幾乎為無上限。
這里采用了DelayedWorkQueue任務隊列,也是定時任務的核心,留在后面分析。

ScheduledThreadPoolExecutor實現了ScheduledExecutorService 中的接口:

public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

延時執行Callable任務的功能。

 public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

延時執行Runnable任務的功能。

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

可以延時循環執行周期性任務。

假設任務執行時間固定2s,period為1s,因為任務的執行時間大于規定的period,所以任務會每隔2s(任務執行時間)開始執行一次。如果任務執行時間固定為0.5s,period為1s,因為任務執行時間小于period,所以任務會每隔1s(period)開始執行一次。實際任務的執行時間即可能是大于period的,也可能小于period,scheduleAtFixedRate的好處就是每次任務的開始時間間隔必然大于等于period。

假設一項業務需求每天凌晨3點將數據庫備份,然而數據庫備份的時間小于24H,最適合用scheduleAtFixedRate方法實現。

    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

可以延時以相同間隔時間循環執行任務。

假設任務執行的時間固定為2s,delay為1s,那么任務會每隔3s(任務時間+delay)開始執行一次。

如果業務需求本次任務的結束時間與下一個任務的開始時間固定,使用scheduleWithFixedDelay可以方便地實現業務。

ScheduledFuture

四個執行任務的方法都返回了ScheduledFuture對象,它與Future有什么區別?

public interface ScheduledFuture<V> extends Delayed, Future<V> {
}

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

public interface Comparable<T> {
    public int compareTo(T o);
}

可以看到ScheduledFuture也繼承了Future,并且繼承了Delayed,增加了getDelay方法,而Delayed繼承自Comparable,所以具有compareTo方法。

四種執行定時任務的方法

schedule(Runnable command,long delay, TimeUnit unit)

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

這個方法中出現了幾個陌生的類,首先是ScheduledFutureTask:

private class ScheduledFutureTask<V>  extends FutureTask<V> implements RunnableScheduledFuture<V> {
            ...
            ScheduledFutureTask(Runnable r, V result, long ns) {
              super(r, result);
              this.time = ns;
              this.period = 0;
              this.sequenceNumber = sequencer.getAndIncrement();
           }
}

public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, ScheduledFuture<V> {
    boolean isPeriodic();
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

這個類是ScheduledThreadPoolExecutor的內部類,繼承自FutureTask實現了RunnableScheduledFuture接口。RunnableScheduledFuture有些復雜,繼承自RunnableFuture和ScheduledFuture接口。可見ScheduledThreadPoolExecutor身兼多職。這個類既可以作為Runnable被線程執行,又可以作為FutureTask用于獲取Callable任務call方法返回的結果。

在FutureTask的構造方法中傳入Runnable對象會將其轉換為返回值為null的Callable對象。

/**
     * Modifies or replaces the task used to execute a runnable.
     * This method can be used to override the concrete
     * class used for managing internal tasks.
     * The default implementation simply returns the given task.
     *
     * @param runnable the submitted Runnable
     * @param task the task created to execute the runnable
     * @param <V> the type of the task's result
     * @return a task that can execute the runnable
     * @since 1.6
     */
    protected <V> RunnableScheduledFuture<V> decorateTask(
        Runnable runnable, RunnableScheduledFuture<V> task) {
        return task;
    }

從decorateTask的字面意義判斷它將具體的RunnableScheduledFuture實現類向上轉型為RunnableScheduledFuture接口。從它的方法描述和實現看出它只是簡單的將ScheduledFutureTask向上轉型為RunnableScheduledFuture接口,由protected 修飾符可知設計者希望子類擴展這個方法的實現。

之所以向上轉型為RunnableScheduledFuture接口,設計者也是希望將具體與接口分離。

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

    boolean canRunInCurrentRunState(boolean periodic) {
        return isRunningOrShutdown(periodic ?
                                   continueExistingPeriodicTasksAfterShutdown :
                                   executeExistingDelayedTasksAfterShutdown);
    }

    private volatile boolean executeExistingDelayedTasksAfterShutdown = true;

    final boolean isRunningOrShutdown(boolean shutdownOK) {
        int rs = runStateOf(ctl.get());
        return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
    }

delayedExecute方法負責執行延時任務。
情況一 : 先判斷線程池是否關閉,若關閉則拒絕任務。
情況二:線程池未關閉,將任務添加到父類的任務隊列,即DelayedWorkQueue中。下面再次判斷線程池是否關閉,并且判斷canRunInCurrentRunState方法的返回值是否為false。因為傳入Runnable參數,task.isPeriodic()為false,所以isRunningOrShutdown返回true。所以這里不會執行到。
情況三:任務成功添加到任務隊列,執行ensurePrestart方法。

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

addWorker已經在ThreadPoolExecutor篇分析過,該方法負責同步將線程池數量+1,并且創建Worker對象添加到HashSet中,最后開啟Worker對象中的線程。因為RunnableScheduledFuture對象已經被添加到任務隊列,Worker中的線程通過getTask方法自然會取到DelayedWorkQueue中的RunnableScheduledFuture任務并執行它的run方法。

這里需要注意的是addWorker方法只在核心線程數未達上限或者沒有線程的情況下執行,并不像ThreadPoolExecutor那樣可以同時存在多個非核心線程,ScheduledThreadPoolExecutor最多只支持一個非核心線程,除非它終止了不會再創建新的非核心線程。

schedule(Callable<V> callable, long delay, TimeUnit unit)

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

    protected <V> RunnableScheduledFuture<V> decorateTask(
        Callable<V> callable, RunnableScheduledFuture<V> task) {
        return task;
    }

     ScheduledFutureTask(Callable<V> callable, long ns) {
            super(callable);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

與schedule(Runnable command,long delay,TimeUnit unit)相比除了可以通過ScheduledFutureTask的get方法得到返回值外沒有區別。

scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)

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

與上述兩個方法的區別在于ScheduledFutureTask的構造函數多了參數period,即任務執行的最小周期:

        ScheduledFutureTask(Runnable r, V result, long ns, long period) {
            super(r, result);
            this.time = ns;
            this.period = period;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit 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;
    }

與scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)的區別是參數delay傳入到ScheduledFutureTask的構造方法中是以負數的形式。

小結

四種延時啟動任務的方法除了構造ScheduledFutureTask的參數不同外,運行機制是相同的。先將任務添加到DelayedWorkQueue 中,然后創建Worker對象,啟動內部線程輪詢DelayedWorkQueue 中的任務。

那么DelayedWorkQueue的add方法是如何實現的,線程輪詢DelayedWorkQueue 調用的poll和take方法又如何實現?

回顧getTask方法獲取任務時的代碼片段:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();

如果我們設置ScheduledThreadPoolExecutor的核心線程數量為0,則執行poll方法。而對于核心線程則執行take方法。

下面分析DelayedWorkQueue 的具體實現。

DelayedWorkQueue

static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
        private static final int INITIAL_CAPACITY = 16;
        private RunnableScheduledFuture<?>[] queue =
            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
        private final ReentrantLock lock = new ReentrantLock();
        private int size = 0;
}

首先DelayedWorkQueue 是ScheduledThreadPoolExecutor的靜態內部類。它的內部有一個RunnableScheduledFuture數組,且初始容量為16.這里提前說明下,queue 數組儲存的其實是二叉樹結構的索引,這個二叉樹其實就是最小堆。

add方法

        public boolean add(Runnable e) {
            return offer(e);
        }

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

        private void grow() {
            int oldCapacity = queue.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
            if (newCapacity < 0) // overflow
                newCapacity = Integer.MAX_VALUE;
            queue = Arrays.copyOf(queue, newCapacity);
        }

        private void setIndex(RunnableScheduledFuture<?> f, int idx) {
            if (f instanceof ScheduledFutureTask)
                ((ScheduledFutureTask)f).heapIndex = idx;
        }

        private void siftUp(int k, RunnableScheduledFuture<?> key) {
            while (k > 0) {
                int parent = (k - 1) >>> 1;
                RunnableScheduledFuture<?> e = queue[parent];
                if (key.compareTo(e) >= 0)
                    break;
                queue[k] = e;
                setIndex(e, k);
                k = parent;
            }
            queue[k] = key;
            setIndex(key, k);
        }

在執行add方法時內部執行的是offer方法,添加RunnableScheduledFuture任務到隊列時先通過內部的ReentrantLock加鎖,因此在多線程調用schedule(Runnable command,long delay, TimeUnit unit)添加任務時也能保證同步。

接下來先判斷隊列是否已滿,若已滿就先通過grow方法擴容。擴容算法是將現有容量*1.5,然后將舊的數組復制到新的數組。(左移一位等于除以2)。

然后判斷插入的是否為第一個任務,如果是就將RunnableScheduledFuture向下轉型為ScheduledFutureTask,并將其heapIndex 屬性設置為0.

如果不是第一個任務,則執行siftUp方法。該方法先找到父親RunnableScheduledFuture對象節點,將要插入的RunnableScheduledFuture節點與之compareTo比較,若父親RunnableScheduledFuture對象的啟動時間小于當前要插入的節點的啟動時間,則將節點插入到末尾。反之會對二叉樹以啟動時間升序重新排序RunnableScheduledFuture接口的實現其實是ScheduledFutureTask類:

    new ScheduledFutureTask<Void>(command, null,triggerTime(delay, unit);

    private long triggerTime(long delay, TimeUnit unit) {
        return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
    }

    /**
     * Returns the trigger time of a delayed action.
     */
    long triggerTime(long delay) {
        return now() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }

    final long now() {
        return System.nanoTime();
    }

第三個參數triggerTime方法返回的就是任務延時的時間加上當前時間。

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

比較的兩個任務的啟動時間。所以DelayedWorkQueue內部的二叉樹是以啟動時間早晚排序的。

poll方法

        public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit)
            throws InterruptedException {
            long nanos = unit.toNanos(timeout);
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    RunnableScheduledFuture<?> first = queue[0];
                    if (first == null) {
                        //情況一 空隊列
                        if (nanos <= 0)
                            return null;
                        else
                            nanos = available.awaitNanos(nanos);
                    } else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)
                            //情況二 已到啟動時間  
                            return finishPoll(first);
                        if (nanos <= 0)
                            //情況三 未到啟動時間,但是線程等待超時
                            return null;
                        first = null; // don't retain ref while waiting
                        if (nanos < delay || leader != null)
                            nanos = available.awaitNanos(nanos);
                        else {
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                long timeLeft = available.awaitNanos(delay);
                                nanos -= delay - timeLeft;
                            } finally {
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && queue[0] != null)
                    available.signal();
                lock.unlock();
            }
        }

非核心線程會通過poll方法同步獲取任務隊列中的RunnableScheduledFuture,如果隊列為空或者在timeout內還等不到任務的啟動時間,都將返回null。如果任務隊列不為空,并且首個任務已到啟動時間線程就能夠獲取RunnableScheduledFuture任務并執行run方法。

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

與非核心線程執行的poll方法相比,核心線程執行的take方法并不會超時,在獲取到首個將要啟動的任務前,核心線程會一直阻塞。

finishPoll方法

        private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
            int s = --size;
            RunnableScheduledFuture<?> x = queue[s];
            queue[s] = null;
            if (s != 0)
                siftDown(0, x);
            setIndex(f, -1);
            return f;
        }

在成功獲取任務后,DelayedWorkQueue的finishPoll方法會將任務移除隊列,并以啟動時間升序重排二叉樹。

小結

DelayedWorkQueue內部維持了一個以任務啟動時間升序排序的二叉樹數組,啟動時間最靠前的任務即數組的首個位置上的任務。核心線程通過take方法一直阻塞直到獲取首個要啟動的任務。非核心線程通過poll方法會在timeout時間內阻塞嘗試獲取首個要啟動的任務,如果超過timeout未得到任務不會繼續阻塞。

這里要特別說明要啟動的任務指的是RunnableScheduledFuture內部的time減去當前時間小于等于0,未滿足條件的任務不會被take或poll方法返回,這也就保證了未到指定時間任務不會執行。

執行ScheduledFutureTask

前面已經分析了schedule方法如何將RunnableScheduledFuture插入到DelayedWorkQueue,Worker內的線程如何獲取定時任務。下面分析任務的執行過程,即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.super.run()方法,即ScheduledFutureTask的父類FutureTask的run方法。FutureTask的run方法已經在ThreadPoolExecutor篇分析過,這里不再多說。

如果執行的是周期型任務,則執行ScheduledFutureTask.super.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(); // 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;
    }

這個方法同run方法比較的區別是call方法執行后不設置結果,因為周期型任務會多次執行,所以為了讓FutureTask支持這個特性除了發生異常不設置結果。

執行完任務后通過setNextRunTime方法計算下一次啟動時間:

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

還記得ScheduledThreadPoolExecutor執行定時任務的后兩種scheduleAtFixedRate和scheduleWithFixedDelay。
scheduleAtFixedRate會執行到情況一,下一次任務的啟動時間最早為上一次任務的啟動時間加period。
scheduleWithFixedDelay會執行到情況二,這里很巧妙的將period參數設置為負數到達這段代碼塊,在此又將負的period轉為正數。情況二將下一次任務的啟動時間設置為當前時間加period。

然后將任務再次添加到任務隊列:

    void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

ScheduledFuture的get方法

既然ScheduledFuture的實現是ScheduledFutureTask,而ScheduledFutureTask繼承自FutureTask,所以ScheduledFuture的get方法的實現就是FutureTask的get方法的實現,FutureTask的get方法的實現分析在ThreadPoolExecutor篇已經寫過,這里不再敘述。要注意的是ScheduledFuture的get方法對于非周期任務才是有效的。

ScheduledThreadPoolExecutor總結

  • ScheduledThreadPoolExecutor是實現自ThreadPoolExecutor的線程池,構造方法中傳入參數n,則最多會有n個核心線程工作,空閑的核心線程不會被自動終止,而是一直阻塞在DelayedWorkQueue的take方法嘗試獲取任務。構造方法傳入的參數為0,ScheduledThreadPoolExecutor將以非核心線程工作,并且最多只會創建一個非核心線程,參考上文中ensurePrestart方法的執行過程。而這個非核心線程以poll方法獲取定時任務之所以不會因為超時就被回收,是因為任務隊列并不為空,只有在任務隊列為空時才會將空閑線程回收,詳見ThreadPoolExecutor篇的runWorker方法,之前我以為空閑的非核心線程超時就會被回收是不正確的,還要具備任務隊列為空這個條件。
  • ScheduledThreadPoolExecutor的定時執行任務依賴于DelayedWorkQueue,其內部用可擴容的數組實現以啟動時間升序的二叉樹。
  • 工作線程嘗試獲取DelayedWorkQueue的任務只有在任務到達指定時間才會成功,否則非核心線程會超時返回null,核心線程一直阻塞。
  • 對于非周期型任務只會執行一次并且可以通過ScheduledFuture的get方法阻塞得到結果,其內部實現依賴于FutureTask的get方法。
  • 周期型任務通過get方法無法獲取有效結果,因為FutureTask對于周期型任務執行的是runAndReset方法,并不會設置結果。周期型任務執行完畢后會重新計算下一次啟動時間并且再次添加到DelayedWorkQueue中。

在源代碼的分析過程中發現分析DelayedWorkQueue還需要有二叉樹的升序插入算法的知識,一開始也沒有認出來這種數據結構,后來又看了別人的文章才了解。這里比較難理解,有興趣的同學可以參考《深度解析Java8 – ScheduledThreadPoolExecutor源碼解析》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容