Java 執行緒池執行原理分析

展翅而飛發表於2016-11-04

上一篇已經對執行緒池的建立進行了分析,瞭解執行緒池既有預設的模板,也提供多種引數支撐靈活的定製。

本文將會圍繞執行緒池的生命週期,分析執行緒池執行任務的過程。

執行緒池狀態

首先認識兩個貫穿執行緒池程式碼的引數:

  • runState:執行緒池執行狀態
  • workerCount:工作執行緒的數量

執行緒池用一個32位的int來同時儲存runState和workerCount,其中高3位是runState,其餘29位是workerCount。程式碼中會反覆使用runStateOf和workerCountOf來獲取runState和workerCount。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 執行緒池狀態
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// ctl操作
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
  • RUNNING:可接收新任務,可執行等待佇列裡的任務
  • SHUTDOWN:不可接收新任務,可執行等待佇列裡的任務
  • STOP:不可接收新任務,不可執行等待佇列裡的任務,並且嘗試終止所有在執行任務
  • TIDYING:所有任務已經終止,執行terminated()
  • TERMINATED:terminated()執行完成

執行緒池狀態預設從RUNNING開始流轉,到狀態TERMINATED結束,中間不需要經過每一種狀態,但不能讓狀態回退。下面是狀態變化可能的路徑和變化條件:

圖1 執行緒池狀態變化路徑

Worker的建立

執行緒池是由Worker類負責執行任務,Worker繼承了AbstractQueuedSynchronizer,引出了Java併發框架的核心AQS。

AbstractQueuedSynchronizer,簡稱AQS,是Java併發包裡一系列同步工具的基礎實現,原理是根據狀態位來控制執行緒的入隊阻塞、出隊喚醒來處理同步。

AQS不會在這裡展開討論,只需要知道Worker包裝了Thread,由它去執行任務。

呼叫execute將會根據執行緒池的情況建立Worker,可以歸納出下圖四種情況:

圖2 worker線上程池裡的四種可能

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    //1
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            //3
            reject(command);
        else if (workerCountOf(recheck) == 0)
            //4
            addWorker(null, false);
    }
    //5
    else if (!addWorker(command, false))
        //6
        reject(command);
}

標記1對應第一種情況,要留意addWorker傳入了core,core=true為corePoolSize,core=false為maximumPoolSize,新增時需要檢查workerCount是否超過允許的最大值。

標記2對應第二種情況,檢查執行緒池是否在執行,並且將任務加入等待佇列。標記3再檢查一次執行緒池狀態,如果執行緒池忽然處於非執行狀態,那就將等待佇列剛加的任務刪掉,再交給RejectedExecutionHandler處理。標記4發現沒有worker,就先補充一個空任務的worker。

標記5對應第三種情況,等待佇列不能再新增任務了,呼叫addWorker新增一個去處理。

標記6對應第四種情況,addWorker的core傳入false,返回撥用失敗,代表workerCount已經超出maximumPoolSize,那就交給RejectedExecutionHandler處理。

private boolean addWorker(Runnable firstTask, boolean core) {
        //1
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        //2
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

標記1的第一段程式碼,目的很簡單,是為workerCount加一。至於為什麼程式碼寫了這麼長,是因為執行緒池的狀態在不斷變化,併發環境下需要保證變數的同步性。外迴圈判斷執行緒池狀態、任務非空和佇列非空,內迴圈使用CAS機制保證workerCount正確地遞增。不瞭解CAS可以看認識非阻塞的同步機制CAS,後續增減workerCount都會使用CAS。

標記2的第二段程式碼,就比較簡單。建立一個新Worker物件,將Worker新增進workers裡(Set集合)。成功新增後,啟動worker裡的執行緒。在finally裡判斷執行緒是否啟動成功,不成功直接呼叫addWorkerFailed。

private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

addWorkerFailed將減少已經遞增的workerCount,並且呼叫tryTerminate結束執行緒池。

Worker的執行

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

public void run() {
    runWorker(this);
}

Worker在建構函式裡採用ThreadFactory建立Thread,在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 {
       //1
        while (task != null || (task = getTask()) != null) {
            w.lock();
           //2
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
               //3
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    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;
                 //4
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;       //5
    } finally {
        //6
        processWorkerExit(w, completedAbruptly);
    }
}

標記1進入迴圈,從getTask獲取要執行的任務,直到返回null。這裡達到了執行緒複用的效果,讓執行緒處理多個任務。

標記2是一個比較複雜的判斷,保證了執行緒池在STOP狀態下執行緒是中斷的,非STOP狀態下執行緒沒有被中斷。如果你不瞭解Java的中斷機制,看如何正確結束Java執行緒這篇。

標記3呼叫了run方法,真正執行了任務。執行前後提供了beforeExecute和afterExecute兩個方法,由子類實現。

標記4裡的completedTasks統計worker執行了多少任務,最後累加進completedTaskCount變數,可以呼叫相應方法返回一些統計資訊。

標記5的變數completedAbruptly表示worker是否異常終止,執行到這裡代表執行正常,後續的方法需要這個變數。

標記6呼叫processWorkerExit結束,後面會分析。

接著來看worker從等待佇列獲取任務的getTask方法:

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        //1
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);
        //2
        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
       //3
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

標記1檢查執行緒池的狀態,這裡就體現出SHUTDOWN和STOP的區別。如果執行緒池是SHUTDOWN狀態,還會先處理完等待佇列的任務;如果是STOP狀態,就不再處理等待佇列裡的任務了。

標記2先看allowCoreThreadTimeOut這個變數,false時worker空閒,也不會結束;true時,如果worker空閒超過keepAliveTime,就會結束。接著是一個很複雜的判斷,好難轉成文字描述,自己看吧。注意一下wc>maximumPoolSize,出現這種可能是在執行中呼叫setMaximumPoolSize,還有wc>1,在等待佇列非空時,至少保留一個worker。

標記3是從等待佇列取任務的邏輯,根據timed分為等待keepAliveTime或者阻塞直到有任務。

最後來看結束worker需要執行的操作:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
   //1
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

  //2
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

   //3
    tryTerminate();

    int c = ctl.get();
    //4
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

正常情況下,在getTask裡就會將workerCount減一。標記1處用變數completedAbruptly判斷worker是否異常退出,如果是,需要補充對workerCount的減一。

標記2將worker處理任務的數量累加到總數,並且在集合workers中去除。

標記3嘗試終止執行緒池,後續會研究。

標記4處理執行緒池還是RUNNING或SHUTDOWN狀態時,如果worker是異常結束,那麼會直接addWorker。如果allowCoreThreadTimeOut=true,並且等待佇列有任務,至少保留一個worker;如果allowCoreThreadTimeOut=false,workerCount不少於corePoolSize。

總結一下worker:執行緒池啟動後,worker在池內建立,包裝了提交的Runnable任務並執行,執行完就等待下一個任務,不再需要時就結束。

執行緒池的關閉

執行緒池的關閉不是一關了事,worker在池裡處於不同狀態,必須安排好worker的”後事”,才能真正釋放執行緒池。ThreadPoolExecutor提供兩種方法關閉執行緒池:

  • shutdown:不能再提交任務,已經提交的任務可繼續執行;
  • shutdownNow:不能再提交任務,已經提交但未執行的任務不能執行,在執行的任務可繼續執行,但會被中斷,返回已經提交但未執行的任務。
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();   //1 安全策略機制
        advanceRunState(SHUTDOWN);   //2
        interruptIdleWorkers();   //3
        onShutdown(); //4 空方法,子類實現
    } finally {
        mainLock.unlock();
    }
    tryTerminate();   //5
}

shutdown將執行緒池切換到SHUTDOWN狀態,並呼叫interruptIdleWorkers請求中斷所有空閒的worker,最後呼叫tryTerminate嘗試結束執行緒池。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();  //1
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

shutdownNow和shutdown類似,將執行緒池切換為STOP狀態,中斷目標是所有worker。drainQueue會將等待佇列裡未執行的任務返回。

interruptIdleWorkers和interruptWorkers實現原理都是遍歷workers集合,中斷條件符合的worker。

上面的程式碼多次出現呼叫tryTerminate,這是一個嘗試將執行緒池切換到TERMINATED狀態的方法。

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        //1
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        //2
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
       //3
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

標記1檢查執行緒池狀態,下面幾種情況,後續操作都沒有必要,直接return。

  • RUNNING(還在執行,不能停)
  • TIDYING或TERMINATED(已經沒有在執行的worker)
  • SHUTDOWN並且等待佇列非空(執行完才能停)

標記2在worker非空的情況下又呼叫了interruptIdleWorkers,你可能疑惑在shutdown時已經呼叫過了,為什麼又呼叫,而且每次只中斷一個空閒worker?你需要知道,shutdown時worker可能在執行中,執行完阻塞在佇列的take,不知道要結束,所有要補充呼叫interruptIdleWorkers。每次只中斷一個是因為processWorkerExit時,還會執行tryTerminate,自動中斷下一個空閒的worker。

標記3是最終的狀態切換。執行緒池會先進入TIDYING狀態,再進入TERMINATED狀態,中間提供了terminated這個空方法供子類實現。

呼叫關閉執行緒池方法後,需要等待執行緒池切換到TERMINATED狀態。awaitTermination檢查限定時間內執行緒池是否進入TERMINATED狀態,程式碼如下:

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}

後言

以上過了一遍執行緒池主要的邏輯,總體來看執行緒池的設計是很清晰的。如有錯誤或不足,歡迎指出,也歡迎留言交流。今次介紹了執行緒池執行的生命週期,下篇會研究更細粒度地控制任務的生命週期,也就是submit和Future。

相關文章