原文地址:www.xilidou.com/2018/02/09/…java
最近在看《Java併發編程的藝術》回顧線程池的原理和參數的時候發現一個問題,若是 corePoolSize = 0 且 阻塞隊列是無界的。線程池將如何工做?編程
咱們先回顧一下書裏面描述線程池execute()
工做的邏輯:微信
看了這四個步驟,其實描述上是有一個漏洞的。若是核心線程數是0,阻塞隊列也是無界的,會怎樣?若是按照上文的邏輯,應該沒有線程會被運行,而後線程無限的增長到隊列裏面。而後呢?併發
因而我作了一下試驗看看到底會怎樣?oop
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。ui
爲了可以使用 ctl 線程池提供了三個方法:this
// 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 這個方法來向線程池提交任務。atom
先看代碼:spa
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);
}
複製代碼
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)
。因此提交一個空線程,直接從阻塞隊列裏面取就能夠了。addWorker(command,false)
,很好理解,對應的就是,阻塞隊列滿了,將任務提交到,非核心線程池。與最大線程池比較。至此,從新概括execute()
的邏輯應該是:
回顧我開始提出的問題:
若是 corePoolSize = 0 且 阻塞隊列是無界的。線程池將如何工做?
這個問題應該就不難回答了。
《Java併發編程的藝術》是一本學習 java 併發編程的好書,在這裏推薦給你們。
同時,但願你們在閱讀技術數據的時候要仔細思考,結合源碼,發現,提出問題,解決問題。這樣的學習才能高效且透徹。
歡迎關注個人微信公衆號