Java提供了幾種便捷的方法建立線程池,經過這些內置的api就可以很輕鬆的建立線程池。在java.util.concurrent
包中的Executors
類,其中的靜態方法就是用來建立線程池的:java
- newFixedThreadPool():建立一個固定線程數量的線程池,並且線程池中的任務所有執行完成後,空閒的線程也不會被關閉。
- newSingleThreadExecutor():建立一個只有一個線程的線程池,空閒時也不會被關閉。
- newCachedThreadPool():建立一個可緩存的線程池,線程的數量爲
Integer.MAX_VALUE
,空閒線程會臨時緩存下來,線程會等待60s
仍是沒有任務加入的話就會被關閉。
Executors
類中還有一些建立線程池的方法(jdk8新加的),可是如今這個觸極到個人知識盲區了~~面試
上面那幾個方法,其實都是建立了一個ThreadPoolExecutor
對象做爲返回值,要搞清楚線程池的原理主要仍是要分析ThreadPoolExecutor
這個類。api
ThreadPoolExecutor
的構造方法:緩存
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... }
ThreadPoolExecutor
的構造方法包含如下幾個參數:安全
- corePoolSize: 核心線程數量,常駐線程池中的線程,即時線程池中沒有任務可執行,也不會被關閉。
- maximumPoolSize:最大線程數量
- keepAliveTime:空閒線程存活時間
- unit: 空閒線程存活時間的單位
- workQueue:工做隊列,線程池一下忙不過來,那新來的任務就須要排隊,排除中的任務就會放在workQueue中
- threadFactory:線程工廠,建立線程用的
- handler:
RejectedExecutionHandler
實例用於在線程池中沒有空閒線程可以執行任務,而且workQueue
中也容不下任務時拒絕任務時的策略。
ThreadPoolExecutor
中的線程統稱爲工做線程,但有一個小概念是核心線程
,核心線程由參數corePoolSize
指定,如corePoolSize
設置5,那線程池中就會有5條線程常駐線程池中,不會被回收掉,可是也會有例外,若是allowCoreThreadTimeOut
爲true
空閒一段時間後,也會被關閉。架構
線程的狀態和工做線程數量
線程中的狀態和工做線程和數量都是由ctl
表示,是一個AtomicInteger
類型的屬性:oop
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl的高四位爲線程的狀態,其餘位數爲工做線程的數量,因此線程中最大的工做線程數量爲(2^29)-1
。學習
線程池中的狀態有五種:ui
- RUNNING:接收新的任務和處理隊列中的任務
- SHUTDOWN:不能新增任務,可是會繼續處理已經添加的任務
- STOP:不能新增任務,不會繼續處理已經添加任務
- TIDYING:全部的任務已經被終止,工做線程爲0
- TERMINATED:terminated()方法執行完成
狀態碼的定義以下:this
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits 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;
建立線程池
若是有面試官問:如何正確的建立線程池?千萬不要說使用Executors
建立線程,雖然Executors
能很方便的建立線程池,可是他提供的靜態建立方法會有一些坑。
主要的緣由是:maximumPoolSize
和workQueue
這兩個參數
Executors
靜態方法在建立線程池時,若是maximumPoolSize
設置爲Integer.MAX_VALUE
,這樣會致使線程池能夠一直要以接收運行任務,可能致使cpu負載太高。
workQueue
是一個阻塞隊列的實例,用於放置正在等待執行的任務。若是在建立線程種時workQueue
實例沒有指定任務的容量,那麼等待隊列中能夠一直添加任務,極有可能致使oom
。
因此建立線程,最好是根據線程池的用途,而後本身建立線程。
添加任務
調用線程池的execute
並非當即執行任務,線程池內部用通過一頓操做,如:判斷核心線程數、是否須要添加到等待隊列中。
下來的代碼是execute
的源碼,代碼很簡潔只有2個if
語句:
public void execute(Runnable command) { 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); }
- 第一個if,若是當前線程池中的工做線程數量小於
corePoolSize
,直接建立一個工做線程執行任務 - 第二個if,當線程池處於運行狀態,調用
workQueue.offer(command)
方法將任務添加到workQueue
,不然調用addWorker(command, false)
嘗試去添加一個工做線程。
整理了一張圖,把線程池分爲三部分Core Worker
、Worker
、workQueue
:
換一種說法,在調用execute
方法時,任務首先會放在Core Worker
內,而後纔是workQueue
,最後纔會考慮Worker
。
這樣作的緣由能夠保證Core Worker
中的任務執行完成後,能當即從workQueue
獲取下一個任務,而不須要啓動別的工做線程,用最少的工做線程辦更多的事。
建立工做線程
在execute
方法中,有三個地方調用了addWorker
。addWorker
方法能夠分爲二部分:
- 增長工做線程數量
- 啓動工做線程
addWorker
的方法簽名以下:
private boolean addWorker(Runnable firstTask, boolean core)
- firstTask:第一個運行的任務,能夠爲空。若是爲空任務會從
workQueue
中獲取。 - core: 是不是核心工做線程
增長工做線程數量
retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); .... 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 } }
上面代碼省略了一部分代碼,主要代碼都在for
循環中,利用CAS
鎖,安全的完成線程池狀態的檢查與增長工做線程的數量。其中的compareAndIncrementWorkerCount(c)
調用就是將工做線程數量+1。
啓動工做線程
增長工做線程的數量後,緊接着就會啓動工做線程:
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); }
啓動工做線程的流程:
- 建立一個
Worker
實例,Worker
構造方法會使用ThreadFactory
建立一個線程
w = new Worker(firstTask); final Thread t = w.thread;
就不說Worker
類的實現了,直接給出構造方法來細品:
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
- 若是線程池狀態是在運行中,或者已經關閉,但工做線程要從
workQueue
中獲取任務,才能添加工做線程
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; }
注意::當線程池處於SHUTDOWN
狀態時,它不能接收新的任務,可是能夠繼續執行未完成的任務。任務是否從workQueue
中獲取,是根據firstTask
判斷,每一個Worker
實例都有一個firstTask
屬性,若是這個值爲null
,工做線程啓動的時候就會從workQueue
中獲取任務,不然會執行firstTask
。
- 啓動線程
調用線程的start
方法,啓動線程。
if (workerAdded) { t.start(); workerStarted = true; }
執行任務
回過頭來看一個Worker
類的定義:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } ... }
Worker
類實現了Runnable
接口,同時在構造方法中會將this
傳遞給線程,到這裏你就知道了Worker
實例中有run
方法,它會在線程啓動後執行:
public void run() { runWorker(this); }
run
方法內部接着調用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 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); } }
- 獲取任務
首先將firstTask
傳遞給task
臨時變量:
Runnable task = w.firstTask;
而後循環檢查task
或者從workQueue
中獲取任務:
while (task != null || (task = getTask()) != null) { ... }
getTask()
稍後再作分析。
- 運行任務
去掉一些狀態檢查、異常捕獲、和勾子方法調用後,保留最重要的調用task.run()
:
while (task != null || (task = getTask()) != null) { ... task.run(); ... }
task
其實就是經過調用execute
方法傳遞進來的Runnable
實例,也就是你的任務。只不過它可能保存在Worker.firstTask
中,或者在workQueue
中,保存在哪裏在前面的任務添加順序
中已經說明。
從workQueue中獲取任務
試想一下若是每一個任務執行完成,就關閉掉一個線程那有多浪費資源,這樣使用線程池也沒有多大的意義。因此線程的主要的功能就是線程複用,一旦任務執行完成直接去獲取下一個任務,或者掛起線程等待下一個提交的任務,而後等待一段時間後仍是沒有任務提交,而後才考慮是否關閉部分空閒的線程。
runWorker
中會循環的獲取任務:
while (task != null || (task = getTask()) != null) { ... task.run(); ... }
上面的代碼getTask()
就是從workQueue
中獲取任務:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { ... int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; ... try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
獲取任務的時候會有兩種方式:
- 超時等待獲取任務
- 一直等待任務,直到有新任務
若是allowCoreThreadTimeOut
爲true
,corePoolSize
指定的核心線程數量會被忽略,直接使用 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
獲取任務,不然的話會根據當前工做線程的數量,若是wc > corePoolSize
爲false
則當前會被認爲是核心線程,調用workQueue.take()
一直等待任務。
工做線程的關閉
仍是在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) { task.run(); } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
- completedAbruptly變量:標記當前工做線程是正常執行完成,仍是異常完成的。completedAbruptly爲
false
能夠肯定線程池中沒有可執行的任務了。
上面代碼是簡潔後的代碼,一個while
循環保證不間斷的獲取任務,沒有任務能夠執行(task爲null)退出循環,最後再纔會調用processWorkerExit
方法:
private void processWorkerExit(Worker w, boolean completedAbruptly) { if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; workers.remove(w); } finally { mainLock.unlock(); } tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } }
processWorkerExit
接收一個Worker
實例與completedAbruptly
變量。processWorkerExit的大體工做流程:
- 判斷當前工做線程是否異常完成,若是是直接減小工做線程的數量,簡單的說就是校訂一下工做線程的數量。
- 增長完成的任務數量,將
Worker
從workers
中移除 - tryTerminate() 檢查線程池狀態,由於線程池能夠延遲關閉,若是你調用
shutdown
方法後不會當即關閉,要等待全部的任務執行完成,因此這裏調用tryTerminate()方法,嘗試去調用terminated
方法。
工做線程完成策略
若是某個工做線程完成,線程池內部會判斷是否須要從新啓動一個:
//判斷線程池狀態 if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { //獲取最小工做線程數量 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //若是最小工做線程數量爲0,可是workQueue中還有任務,那重置最小工做線程數量1 if (min == 0 && ! workQueue.isEmpty()) min = 1; //若是當前工做線程數數量大於或等於最小工做線程數量,則不須要啓動新的工做線程 if (workerCountOf(c) >= min) return; // replacement not needed } //啓動一個新的工做線程 addWorker(null, false); }
工做線程完成後有兩種處理策略:
- 對於異常完成的工做線程,直接啓動一個新的替換
- 對於正常完成的工做線程,判斷當前工做線程是否足夠,若是足夠則不須要新啓動工做線程
注意: 這裏的完成,表示工做線程的任務執行完成,workQueue
中也沒有任務能夠獲取了。
線程池的關閉
關閉線程池有能夠經過shutdown
方法:
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); }
shutdown
方法,第一步就是先改變線程池的狀態,調用advanceRunState(SHUTDOWN)
方法,將線程池當前狀態更改成SHUTDOWN
,advanceRunState代碼以下:
private void advanceRunState(int targetState) { for (;;) { int c = ctl.get(); if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) break; } }
而後當即調用interruptIdleWorkers()
方法,interruptIdleWorkers()
內部會調用它的重載方法interruptIdleWorkers(boolean onlyOne)
同時onlyOne參數傳遞的false
來關閉空閒的線程:
private void interruptIdleWorkers() { interruptIdleWorkers(false); } private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
以上代碼會遍歷workers
中的Worker
實例,而後調用線程的interrupt()
方法。
什麼樣的線程纔是空閒工做線程?
前面提到過在getTask()
中,線程從workQueue
中獲取任務時會阻塞,被阻塞的線程就是空閒的。
再次回到getTask()
的代碼中:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { // 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; ... try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
再次分析getTask()
中的代碼中有一段捕獲InterruptedException
的代碼塊,interruptIdleWorkers方法中斷線程後,getTask()
會捕獲中斷異常,由於外面是一個for
循環,隨後代碼走到判斷線程池狀態的地方:
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; }
上面的代碼的會判斷當前線程池狀態,若是狀態大於STOP
或者狀態等於SHUTDOWN
而且workQueue
爲空時則返回null
,getTask()
返回空那麼在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) { task.run(); } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
shutdownNow
除了shutdown方法能關閉線程池,還有shutdownNow
也能夠關閉線程池。它兩的區別在於:
shutdownNow
會清空workQueue
中的任務shutdownNow
還會停止當前正在運行的任務shutdownNow
會使線程進入STOP
狀態,而shutdown()
是SHUTDOWN
狀態
public List<runnable> shutdownNow() { List<runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(STOP); interruptWorkers(); tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
上面代碼基本流程:
- advanceRunState(STOP): 使線程池進行
STOP
狀態,與shutdown()
中的一致 ,只是使用的狀態碼是STOP
- interruptWorkers(): 與
shutdown()
中的一致 - drainQueue(): 清空隊列
任務是停止執行仍是繼續執行?
調用shutdownNow()後線程池處於STOP
狀態,緊接着全部的工做線程都會被調用interrupt
方法,若是此時runWorker
還在運行會發生什麼?
在runWorker
有一段代碼,就是工做線程停止的重要代碼:
final void runWorker(Worker w) { ... while (task != null || (task = getTask()) != null) { if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); task.run(); } ... }
重點關注:
if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt();
這個if看起來有點難理解,理解下來大體意思是:若是線程池狀態大於等於STOP
,當即中斷線程,不然清除線程的中斷標記,也就是說當線程池狀態爲RUNNING
和SHUTDOWN
時,線程的中斷標記會被清除(線程的中斷代碼在interruptWorkers
方法中),能夠繼續執行任務。
以上代碼執行完成後,緊接着就會調用task.run()
方法,這裏面咱們本身就能夠根據線程的中斷標記來判斷任務是否被中斷。
總結
我的水平有限,文中若有錯誤,謝謝你們指正。
本文從線程池的源碼入手,分析線程池的建立、添加任務、運行任務等流程,整個分析下來基本上大多數公司關於線程池面試的問題均可以回答得上來,固然還有一些小細節如:Worker
類是繼承AQS
的,爲何這麼作其實源碼中都有一些苗頭,Worker
在運行時會鎖住運行的代碼塊,而shutdown
在關閉空閒的Worker
時,首先就要去獲取Worker
的同步鎖才能繼續操做,這樣才能安全的關閉工做線程。
歡迎關注個人公衆號:架構文摘,得到獨家整理120G的免費學習資源助力你的架構師學習之路!
公衆號後臺回覆
arch028
獲取資料: