原文地址:www.xilidou.com/2018/02/09/…
最近在看《Java併發程式設計的藝術》回顧執行緒池的原理和引數的時候發現一個問題,如果 corePoolSize = 0 且 阻塞佇列是無界的。執行緒池將如何工作?
我們先回顧一下書裡面描述執行緒池execute()
工作的邏輯:
- 如果當前執行的執行緒,少於corePoolSize,則建立一個新的執行緒來執行任務。
- 如果執行的執行緒等於或多於 corePoolSize,將任務加入 BlockingQueue。
- 如果 BlockingQueue 內的任務超過上限,則建立新的執行緒來處理任務。
- 如果建立的執行緒數是單錢執行的執行緒超出 maximumPoolSize,任務將被拒絕策略拒絕。
看了這四個步驟,其實描述上是有一個漏洞的。如果核心執行緒數是0,阻塞佇列也是無界的,會怎樣?如果按照上文的邏輯,應該沒有執行緒會被執行,然後執行緒無限的增加到佇列裡面。然後呢?
於是我做了一下試驗看看到底會怎樣?
public class threadTest {
private final static ThreadPoolExecutor executor = new ThreadPoolExecutor(0,1,0, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
while (true) {
executor.execute(() -> {
System.out.println(atomicInteger.getAndAdd(1));
});
}
}
}
複製程式碼
結果裡面的System.out.println(atomicInteger.getAndAdd(1));
語句執行了,與上面的描述矛盾了。到底發生了什麼?執行緒池建立執行緒的邏輯是什麼?我們還是從原始碼來看看到底執行緒池的邏輯是什麼?
ctl
要了解執行緒池,我們首先要了解的執行緒池裡面的狀態控制的引數 ctl。
- 執行緒池的ctl是一個原子的 AtomicInteger。
- 這個ctl包含兩個引數 :
- workerCount 啟用的執行緒數
- runState 當前執行緒池的狀態
- 它的低29位用於存放當前的執行緒數, 因此一個執行緒池在理論上最大的執行緒數是 536870911; 高 3 位是用於表示當前執行緒池的狀態, 其中高三位的值和狀態對應如下:
- 111: RUNNING
- 000: SHUTDOWN
- 001: STOP
- 010: TIDYING
- 110: TERMINATED
為了能夠使用 ctl 執行緒池提供了三個方法:
// Packing and unpacking ctl
// 獲取執行緒池的狀態
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 獲取執行緒池的工作執行緒數
private static int workerCountOf(int c) { return c & CAPACITY; }
// 根據工作執行緒數和執行緒池狀態獲取 ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
複製程式碼
execute
外界通過 execute 這個方法來向執行緒池提交任務。
先看程式碼:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果工作執行緒數小於核心執行緒數,
if (workerCountOf(c) < corePoolSize) {
//執行addWork,提交為核心執行緒,提交成功return。提交失敗重新獲取ctl
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果工作執行緒數大於核心執行緒數,則檢查執行緒池狀態是否是正在執行,且將新執行緒向阻塞佇列提交。
if (isRunning(c) && workQueue.offer(command)) {
//recheck 需要再次檢查,主要目的是判斷加入到阻塞隊裡中的執行緒是否可以被執行
int recheck = ctl.get();
//如果執行緒池狀態不為running,將任務從阻塞佇列裡面移除,啟用拒絕策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果執行緒池的工作執行緒為零,則呼叫addWoker提交任務
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//新增非核心執行緒失敗,拒絕
else if (!addWorker(command, false))
reject(command);
}
複製程式碼
addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
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;
//CAS 增加工作執行緒數
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
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
//上面的邏輯是考慮是否能夠新增執行緒,如果可以就cas的增加工作執行緒數量
//下面正式啟動執行緒
try {
//新建worker
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());
// rs < SHUTDOWN ==> 執行緒處於RUNNING狀態
// 或者執行緒處於SHUTDOWN狀態,且firstTask == null(可能是workQueue中仍有未執行完成的任務,建立沒有初始任務的worker執行緒執行)
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 當前執行緒已經啟動,丟擲異常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//workers 是一個 HashSet 必須在 lock的情況下操作。
workers.add(w);
int s = workers.size();
//設定 largeestPoolSize 標記workAdded
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果新增成功,啟動執行緒
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//啟動執行緒失敗,回滾。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
複製程式碼
先看看 addWork()
的兩個引數,第一個是需要提交的執行緒 Runnable firstTask,第二個引數是 boolean 型別,表示是否為核心執行緒。
execute() 中有三處呼叫了 addWork()
我們逐一分析。
- 第一次,條件
if (workerCountOf(c) < corePoolSize)
這個很好理解,工作執行緒數少於核心執行緒數,提交任務。所以addWorker(command, true)
。 - 第二次,如果
workerCountOf(recheck) == 0
如果worker的數量為0,那就addWorker(null,false)
。為什麼這裡是null
?之前已經把 command 提交到阻塞佇列了workQueue.offer(command)
。所以提交一個空執行緒,直接從阻塞佇列裡面取就可以了。 - 第三次,如果執行緒池沒有 RUNNING 或者 offer 阻塞佇列失敗,
addWorker(command,false)
,很好理解,對應的就是,阻塞佇列滿了,將任務提交到,非核心執行緒池。與最大執行緒池比較。
至此,重新歸納execute()
的邏輯應該是:
- 如果當前執行的執行緒,少於corePoolSize,則建立一個新的執行緒來執行任務。
- 如果執行的執行緒等於或多於 corePoolSize,將任務加入 BlockingQueue。
- 如果加入 BlockingQueue 成功,需要二次檢查執行緒池的狀態如果執行緒池沒有處於 Running,則從 BlockingQueue 移除任務,啟動拒絕策略。
- 如果執行緒池處於 Running狀態,則檢查工作執行緒(worker)是否為0。如果為0,則建立新的執行緒來處理任務。如果啟動執行緒數大於maximumPoolSize,任務將被拒絕策略拒絕。
- 如果加入 BlockingQueue 。失敗,則建立新的執行緒來處理任務。
- 如果啟動執行緒數大於maximumPoolSize,任務將被拒絕策略拒絕。
總結
回顧我開始提出的問題:
如果 corePoolSize = 0 且 阻塞佇列是無界的。執行緒池將如何工作?
這個問題應該就不難回答了。
最後
《Java併發程式設計的藝術》是一本學習 java 併發程式設計的好書,在這裡推薦給大家。
同時,希望大家在閱讀技術資料的時候要仔細思考,結合原始碼,發現,提出問題,解決問題。這樣的學習才能高效且透徹。
歡迎關注我的微信公眾號