[懷舊并發04]分析Java線程池執行原理

Java并發編程源碼分析系列:

上一篇已經對線程池的創建進行了分析,了解線程池既有預設的模板,也提供多種參數支撐靈活的定制。

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

線程池狀態

首先認識兩個貫穿線程池代碼的參數:

  • 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并發包里一系列同步工具的基礎實現,原理是根據狀態位來控制線程的入隊阻塞、出隊喚醒來處理同步。

Worker利用AQS的功能實現對獨占線程變量的設置,這是一個需要同步的過程。AQS不會在這里展開討論,有興趣的同學可以去看分析CountDownLatch的實現原理,里面詳細介紹了AQS的實現原理。

調用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。

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

推薦閱讀更多精彩內容