Java線程池ThreadPoolExecutor深度探索及源碼解析

我們的程序里,時常要使用多線程。因此多線程的管理變的尤為重要。ThreadPoolExecutor很好的解決了這一點。本篇文章主要從源碼入手,分析ThreadPoolExecutor的原理。

1.標記和構造方法####

和很多狀態對象一樣,ThreadPoolExecutor也通過一個int的頭3位來記錄線程池的狀態,后面20多位來標記工作線程數量。并且提供通用的位運算接口來獲得你所需要的數據。

    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;

我們先來看下ThreadPoolExecutor的構造方法,這里似乎我們又要老生常談了,網上已經有很多關于線程池各個參數的介紹了,這里,非墨還是會再說一遍,這樣加深一下大家的印象。

public ThreadPoolExecutor(int corePoolSize, //核心線程數量
                              int maximumPoolSize,//最大線程數量
                              long keepAliveTime,//存活時間
                              TimeUnit unit,//存活時間單位
                              BlockingQueue<Runnable> workQueue,//工作隊列
                              ThreadFactory threadFactory,//線程構造工廠
                              RejectedExecutionHandler handler//拒絕請求回調
) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

2.執行流程####

按照我們熟知的線程池機制,
1.當請求被post到我們的線程池中,我們的線程池會先生成一個核心線程來執行它
2.當核心線程滿了的時候,將會把這個請求放入到我們的工作請求隊列workQueue中。
3.如果你提供的隊列是一個有界隊列的時候,線程池將會判斷你的最大線程數是否超過你的核心線程。如果超過核心線程的話,線程池會生成新的線程去執行它。
4.如果這個時候,已經達到了最大線程數,那么線程池將走到拒絕回調
5.如果線程池的最大線程數不大于核心線程數,并且工作隊列已滿,那么將直接走拒絕回調

實際上這個流程已經在ThreadPoolExecutor.execute方法注釋中有詳細的說明。即便沒有說明,我們也可以從它的代碼流程簡單看出一些端倪:

//code ThreadPoolExecutor.execute(Runnable)
        int c = ctl.get();//獲取當前運行線程數
        if (workerCountOf(c) < corePoolSize) {//如果當前線程數小于核心線程數
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {//將請求命令加入到工作隊列
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))//加入到非核心線程中
            reject(command);//如果非核心線程沒有執行,那么將走拒絕請求回調

3.深入源碼###

我們以execute為入口,深入分析一下這個線程池的源碼。int c = ctl.get()方法我們暫時不說,后面我們將會補充,我們暫且把它理解為獲得一個數量,而這個數量c將會傳入到workerCountOf方法中。這個方法名稱我們就能知道其用意,就是為了獲得當前工作線程數量。

private static int workerCountOf(int c)  { return c & CAPACITY; }

上文我們說到,線程池會通過一個int的后幾位來記錄線程數量,而workerCountOf就是通過位運算來獲得當前工作線程數。在獲得當前線程數了以后,如果當前線程數小于
corePoolSize的話,將會通過addWorker方法把command加入到工作線程中。addWorker需要提供兩個參數,一個是你的command,另外一個boolean量是為了標識是否是往core線程中加。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();//獲得一個含有狀態和數量的值
            int rs = runStateOf(c);//獲得當前線程池狀態
            ...
           for (;;) {//第二個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
            }
        }
   }

這里,通過上面的代碼我們可以清晰的了解ctl變量的存在的目的:
1.首先,當從類型上看clt是一個原子類型,說明它是要支持多線程調用的
2.ctl里面的值需要存儲兩個信息,一個是線程數量,一個是當前線程池的工作狀態。

這時候是否有讀者還在納悶,為什么我的線程數小于我的核心線程數,我往我的線程池里加,還是可能出現加不進去的情況。事實上,“第二個for”循環已經很好的說明了這一點。因為線程池不能保證是同一個線程調用addWorker方法。線程池需要同步過后,才能保證是否是否往核心線程里面加。這就是為什么在ThreadPoolExecutor.execute方法里,在判斷完核心線程數量之后,如果失敗了,還要再取一次當前線程數的原因。

 if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();//再取一次
        }

好的,我們繼續回到"第二個for"。我們可以看出,線程池在同步方面不僅細化了粒度,而且用的是CAS算法。這種算法可以勁量的避免由于sync引起的線程阻塞。

for (;;) {//第二個for
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
               //當線程池數量超過核心線程的時候退出,返回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
            }

由于我們現在只有一個線程在工作,不存在多線程競爭的情況,因此我們選擇跳出循環的邏輯。跳出循環以后,程序將真正意義上的生成一個Worker線程來執行指令。

//code private boolean addWorker(Runnable firstTask, boolean core)
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);//生成一個worker對象
            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);//將Worker線程納入workers集合對象管理
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;//重新賦值largestPoolSize變量
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;

上面的代碼非常簡單,線程池將生成一個Worker的線程包裝類。不論是是否是核心線程,所有的線程都被納入到workers集合對象進行管理。如果一切流程都正常workerAdded將為true,Worker里的線程將被啟動。啟動后Worker將執行線程的run方法,而在run方法中,又調用到Worker的runWorker(Worker)方法:

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

runworker是真正的線程執行流程的代碼段:

// code runworker
 try {
            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 {
                        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);
        }

runworker方法中引申出來兩個方法beforeExecute和afterExecute??梢酝ㄟ^繼承的方式來監控command的執行。相當于在command.run之前和之后切了兩個面,是一種面向方面的編程模式。當Task執行完成之后,由于while循環,將再次執行while的判斷條件task = getTask()) != null; getTask方法是可能阻塞的,阻塞的時間是根據你在構造線程池的時候設置的超時時間來決定的。

private Runnable getTask() {
        boolean timedOut = false; //是否判斷超時

        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;
            //allowCoreThreadTimeOut變量用于控制是否讓核心線程也進行超時判斷
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?//通過timed變量來選擇使用poll方法還是take方法
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;//如果poll獲取的r為空,標記為超時
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }


>getTask還是一個循環操作,第一次執行的時候,會通過timed變量來判斷是否有超時檢查,如果有超時檢查的話將調用poll方法。如果poll在規定的時間內并沒有獲得任何的執行對象,返回的r為null,timedOut將被標記為true。這時候,又再次進入循環。這時候,如果你是非核心線程,是擴展線程的話,那么,if ((wc > maximumPoolSize || (timed && timedOut))這個判斷為true,程序將返回一個null。
在runWorker方法中,如果getTask返回的對象為null,runWorker將跳出while循環,執行finally語句:
   finally {
        processWorkerExit(w, completedAbruptly);
    }

>processWorkerExit方法需要傳遞兩個變量,第一個變量是Worker對象,第二個變量是completedAbruptly變量,這個變量是干什么用的呢?因為你的程序跳出可能存在兩種情況,一種是正常跳出,一種是異常跳出,如果是異常跳出的話,這個時候你的workercount未必正常的執行decrement操作,因此通過這個變量來標記程序的執行狀態。

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

mainLock是一個全局鎖,主要是為了同步全局的workers變量。上面的代碼中,線程池將記錄一下task執行數據,并且將worker從workers隊列中刪除。
這個時候,基本上整個線程池的流程都已經概述完了,當然,我們還確實一個變量,那就是RejectedExecutionHandler類型變量。這個得回到我們的execute方法:

if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);//拒絕請求



>當線程池拒絕請求的時候,將調用reject方法,而reject方法將會回調RejectedExecutionHandler的rejectedExecution方法:

final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}

線程池提供一個默認的拒絕請求回調:

//code ThreadPoolExecutor
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
//code AbortPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}

也就是采用異常的方式來拒絕請求。


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

推薦閱讀更多精彩內容