線程池:Executor框架源碼解析
2.源碼解析
ThreadPoolExecutor類*
5)任務執行—execute方法
線程池在使用過程中提交任務使用的是submit
方法,但是該方法本身不是 ThreadPoolExecutor類實現的,而是其父類 AbstractExecutorService類實現的,具體見筆記。該方法內部調用的 execute
方法是需要子類自行實現。
execute
方法的源碼如下,
// submit方法向execute方法中傳入的參數實際上是FutureTask對象
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
// 發現可運行的線程數是 0,就初始化一個線程,這裏是個極限情況,入隊的時候,突然發現
// 可用線程都被回收了
else if (workerCountOf(recheck) == 0)
// Runnable是空的,不會影響新增線程,但是線程在 start 的時候不會運行
// Thread.run() 裏面有判斷
addWorker(null, false);
}
// 隊列滿了,開啓線程到 maxSize,如果失敗直接拒絕,
else if (!addWorker(command, false))
reject(command);
}
AbstractExecutorService的submit
方法向execute
方法中傳入的實際上是newTaskFor
方法創建的 FutureTask類對象,屬於Runnable的子類。
該方法分3步對傳入的任務進行處理,
- 如果工作線程數小於
corePoolSize
,嘗試創建新的線程並將FutureTask類對象傳入線程中。這也就解決了初始化時沒有在線程池中創建線程的問題。這一過程調用的是 ThreadPoolExecutor的addWorker
方法,將任務加入到任務隊列中,該方法具體說明見下方筆記 - 任務被成功加入到任務隊列後,再次檢查線程池狀態(因爲最後一次檢查可能有線程已經死了)。如果
isRunning
方法返回爲 false,則從任務隊列中移除剛添加的任務並調用reject
方法拒絕該任務。如果工作線程數爲0,則重新創建一個線程 - 如果無法將任務加入到
workQueue
,則嘗試調用addWorker
方法,但不作爲核心線程。如果依舊無法添加,拒絕該task(此時因爲沒有加入到workQueue
中,所以無需移除)
6)新建Worker並執行任務—addWorker方法
addWorker
方法傳入參數說明,
private boolean addWorker(Runnable firstTask, boolean core)
參數 | 說明 |
---|---|
firstTask | submit -> newTaskFor -> execute -> addWorker,將最初的Runnable或Callable對象封裝得到的FutureTask對象 |
core | 是否爲核心線程,true表示是核心線程;反之,爲非核心線程 |
addWorker
方法的主要執行邏輯如下,分兩步進行解讀
更新Worker的數量
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
}
}
retry
是一個標記,和循環配合使用,continue retry 的時候,會跳到 retry處再次執行。如果 break retry,則跳出整個循環體。前
ThreadPoolExecutor 把狀態和線程池數量2個屬性存在了ctl
變量中。
- 源碼中先檢查了線程池狀態,線程池狀態不正常返回 false
- 然後根據創建線程類型的不同(即是否是核心線程),進行數量的校驗。如果數量超過設定數目,返回false
- 通過 CAS方式更新狀
ctl
,成功的話則跳出循環。否則再次取得線程池狀態,如果和上一次的狀態不一致,那麼從頭開始執行。如果狀態並未改變則繼續CAS更新 worker 的數量。
數目更新的流程參考下圖,
添加worker到workers集合中並且啓動worker持有的線程
該過程源碼如下,
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;
- 將FutureTask對象傳入到Worker中,創建新的worker對象
- 可以看到添加 worker 時需要先獲得鎖,確保併發安全
- 對線程池的狀態進行檢驗。如果線程池狀態不對,則調用
addWorkFailed
方法,解鎖並返回false - 進一步檢驗線程是否能夠被啓動,如果不能,則拋出
IllegalThreadStateException
異常並執行addWorkFailed
方法,結束執行前解鎖 - 將worker加入到workers集合中,調用 worker 中封裝的線程的
start
方法啓動線程。
上述過程失敗調用的 addWorkerFailed
方法是對之前操作進行回滾的方法。
7)線程任務實際運行—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 {
while (task != null || (task = getTask()) != null) {
w.lock();
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);
}
}
上述代碼具體流程總結如下,
- 取出worker中的 firstTask(FutureTask類對象)賦值給變量task,並使Worker對象的firstTask指向 null
- 如果沒有firstTask,則調用
getTask
方法從workQueue
中獲取task - 獲取 worker對象的鎖,因爲Worker本身是AQS的子類,所以具有鎖的特性
- 執行
beforeExecute
方法,該方法在ThreadPoolExecutor中是空方法,如有需要在子類實現 - 執行
task.run
,調用FutureTask類的run方法。FutureTask的run
方法參見筆記,無論是Runnable還是Callable都能夠執行 - 執行
afterExecute
方法,同樣在ThreadPoolExecutor中是空方法,如有需要在子類實現 - 內層finally代碼塊中清空task,worker完成的任務數目+1,釋放鎖
- 當有異常或者沒有 task 可執行時,進入到外層 finally 代碼塊中調用
processWorkerExit
方法退出當前 worker。從 workers 中移除本 worker 後,如果 worker 數量小於corePoolSize
,則創建新的 worker,以維持corePoolSize
大小的線程數。
下面這行代碼
while (task != null || (task = getTask()) != null)
確保了 worker 不停地從 workQueue 中取得 task 執行。getTask
方法會從 workQueue 中 調用poll
或者 take
取出其中的 task。
8)獲取workQueue的任務—getTask方法
從任務隊列中獲取Runnable對象,
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//線程池關閉 && 隊列爲空,不需要在運行了,直接放回
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// true 運行的線程數大於 coreSize || 核心線程也可以被滅亡
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 隊列以 LinkedBlockingQueue 爲例,timedOut 爲 true 的話說明下面 poll 方法執行返回的是 null
// 說明在等待 keepAliveTime 時間後,隊列中仍然沒有數據
// 說明此線程已經空閒了 keepAliveTime 了
// 再加上 wc > 1 || workQueue.isEmpty() 的判斷
// 所以使用 compareAndDecrementWorkerCount 方法使線程池數量減少 1
// 並且直接 return,return 之後,此空閒的線程會自動被回收
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 從隊列中阻塞拿 worker
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 設置已超時,說明此時隊列沒有數據
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
代碼兩處關鍵說明,
- 使用隊列的
poll
或take
方法從隊列中拿數據,根據隊列的特性,隊列中有任務可以返回,隊列中無任務會阻塞 - 方法中的第二個 if 判斷,說的是在滿足一定條件下(條件看註釋),會減少空閒的線程,減少的手段是使可用線程數減一,並且直接 return null,說明該線程空閒超過時間限制且當前無任務可執行,可銷燬該線程。返回後線程結束執行,JVM 會自動回收該線程
3.任務拒絕策略
execute
方法在線程池已滿且任務隊列已滿的條件下會發生線程池拒絕執行任務的情況,調用的是ThreadPoolExecutor的reject
方法,該方法源碼如下,
// 方法的參數是傳入execute方法的FutureTask類對象
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
上面代碼中的handler
對象是初始化過程中指定的拒絕執行處理類,在 ThreadPoolExecutor中實現了4種拒絕執行處理類。這4類拒絕執行類均實現了 RejectedExecutionHandler接口。
RejectedExecutionHandler接口
該接口中只定義了一個方法,
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
ThreadPoolExecutor內部拒絕執行類
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
類名 | 說明 |
---|---|
AbortPolicy | 當線程池和隊列都滿時,再有任務進來直接拋出RejectedExecutionException 異常,是默認的拒絕處理類 |
CallerRunsPolicy | 當線程池和隊列都滿時,任務將會被任務的調用方線程執行(如主線程),如果線程池關閉,那麼任務將會被拋棄 |
DiscardPolicy | 當線程池和隊列都滿時,再有任務進來時,直接將任務拋棄且不會有任何返回結果 |
DiscardOldestPolicy | 當線程池和隊列都滿時,再有任務進來,拋棄最老的未處理的任務,然後重試該新進來的任務,如果線程池關閉,那麼任務將會被拋棄 |
使用示例
public static void main(String [] args) throws ExecutionException, InterruptedException {
// 創建一個線程池,分別指定不同的拒絕處理類
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1,
500,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy()
//new ThreadPoolExecutor.DiscardPolicy()
//new ThreadPoolExecutor.DiscardOldestPolicy()
//new ThreadPoolExecutor.AbortPolicy()
);
// 提交第1個任務
Future<String> taskOne = threadPoolExecutor.submit(()->{
System.out.println("taskOne start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("taskOne 沉睡2秒結束...");
return Thread.currentThread().getName();
});
// 提交第2個任務
Future<String> taskTwo = threadPoolExecutor.submit(()->{
System.out.println("taskTwo start...");
return Thread.currentThread().getName();
});
// 提交第3個任務
Future<String> taskThree = threadPoolExecutor.submit(()->{
System.out.println("taskThree start...");
return Thread.currentThread().getName();
});
System.out.println("taskOne:" + taskOne.get());
System.out.println("taskTwo:" + taskTwo.get());
System.out.println("taskThree:" + taskThree.get());
threadPoolExecutor.shutdown();
}
上述例子中創建了一個核心線程池數與最大線程池數都爲1,阻塞隊列長度也爲1的線程池。然後啓動三個任務,其中第一個任務執行等待2秒。
taskOne
佔用了線程池中的唯一的線程,taskTwo
進入阻塞隊列,這時隊列已滿,taskThree
再進入線程池觸發對應的拒絕策略。
- 使用AbortPolicy策略
Exception in thread “main” java.util.concurrent.RejectedExecutionException: …
taskOne start…
taskOne 沉睡2秒結束…
taskTwo start…
主線程在taskThree的submit()
時拋出異常,後面的信息都不打印了
- 使用CallerRunsPolicy策略
taskOne start…
taskThree start…
taskOne 沉睡2秒結束…
taskOne:pool-1-thread-1
taskTwo start…
taskTwo:pool-1-thread-1
taskThree:main
可以看出taskOne
和taskTwo
的執行都是線程池內的那個線程完成的,而taskThree
的執行線程則是調用taskThree
的main函數主線程
- 使用DiscardPolicy策略
taskOne start…
taskOne 沉睡2秒結束…
taskOne:pool-1-thread-1
taskTwo start…
taskTwo:pool-1-thread-1
默默拋棄策略不再打印taskThree
相關信息,並且程序在taskThree.get()
處阻塞,線程池遲遲無法關閉。
- 使用DiscardOldestPolicy策略
taskOne start…
taskOne 沉睡2秒結束…
taskOne:pool-1-thread-1
taskThree start…
taskThree
再進入線程池時,線程池根據策略把未執行的taskTwo
給拋棄了,然後執行了taskThree
,所以打印了 “taskThree start…” 。但是,因爲taskTwo
已被拋棄所以在調用taskTwo.get()
時發生了阻塞。所以沒有打印main方法中執行後的相關的信息,且線程池始終無法關閉。