ScheduledThreadPoolExecutor JDK定時任務線程池

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線程,這顯然是沒有必要的。

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