我們的程序里,時常要使用多線程。因此多線程的管理變的尤為重要。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的主要源碼和結構已經分析完了,當然還有其他的特性和功能需要看官們自己去探索。
-----非墨