ThreadPoolExecutor
ThreadPoolExecutor是ExecutorService的一種實現,可以用若干已經池化的執行緒執行被提交的任務。使用執行緒池可以幫助我們限定和整合程式資源,儘可能避免建立新的執行緒來執行任務從而降低任務呼叫的開銷,在執行大量非同步任務的時候反而能獲得更好的效能。此外,ThreadPoolExecutor還會維護一些統計資訊,比如已完成的任務數量。
juc包的作者Doug Lea推薦程式設計師儘量使用更為便利的Executors類的工廠方法來配置執行緒池:
- Executors.newCachedThreadPool():建立一個執行緒池,可以根據執行緒池的需要來建立任務。這個執行緒池比較適用執行週期較短且量大的非同步任務,在呼叫execute(...)方法時如果執行緒池中存在閒置的執行緒,將複用閒置執行緒,否則建立一個新執行緒執行任務。如果執行緒的閒置時間超過60s,執行緒將被終止並從執行緒池內移除。因此,該執行緒池即便空閒時間再長,也不會有資源的消耗。
- Executors.newFixedThreadPool(int nThreads):建立一個執行緒池,nThreads為池內執行緒的數量,池內最多同時有nThreads個執行緒並行處理任務,如果有新的任務提交到執行緒池,會先暫存線上程池中的無邊界任務佇列進行等待,直到有執行緒可用。如果有執行緒在執行期間因為錯誤提前終止,執行緒池將啟動一個新的執行緒代替原先的執行緒繼續處理任務佇列中處於等待的任務。除非顯式呼叫shutdown(),否則執行緒池中的執行緒將一直存在。
- Executors.newSingleThreadExecutor():建立一個Executor,該Executor使用一個工作執行緒處理任務。如果執行緒在執行期間因為錯誤而終止,將啟動一個新的執行緒代替原先的執行緒繼續處理無邊界任務佇列中處於等待的任務。佇列中的任務是按順序執行,任何時刻都不會有多個任務處於活躍狀態。與newFixedThreadPool(1)不同,newFixedThreadPool(int nThreads)生成的執行緒池,可以強轉為ThreadPoolExecutor型別,再呼叫setCorePoolSize(int corePoolSize)方法設定核心執行緒數,而newSingleThreadExecutor()的實現類為FinalizableDelegatedExecutorService,無法直接設定核心執行緒數。
上面三種是較為常見的配置執行緒池的工廠方法,如果有需要根據業務場景特殊配置執行緒池的,請看下面的引數:
核心執行緒數和最大執行緒數
ThreadPoolExecutor將根據corePoolSize(核心執行緒數)和maximumPoolSize(最大執行緒數)設定的邊界自動調整執行緒池內工作執行緒的數量(通過getPoolSize()),corePoolSize可以通過getCorePoolSize()、setCorePoolSize(int corePoolSize)獲取和設定核心執行緒數,maximumPoolSize可以通過getMaximumPoolSize()、setMaximumPoolSize(int maximumPoolSize)獲取和設定最大執行緒數。當有新的任務提交時,如果工作執行緒少於核心執行緒數,將會建立一個新執行緒來執行該任務,即便其他工作執行緒處於閒置狀態。如果工作執行緒多於corePoolSize但少於maximumPoolSize,則當任務佇列滿的時候才會建立新執行緒。如果corePoolSize和maximumPoolSize數值一樣,則建立一個固定大小的執行緒池;如果將maximumPoolSize設定為Integer.MAX_VALUE,則執行緒池可以容納任意數量的併發任務。
按需構造
核心執行緒只有當有新任務到達時才會建立,但我們可以重寫prestartCoreThread() 或者prestartAllCoreThreads()來預先啟動核心執行緒。如果在構造一個執行緒池時,傳入的任務佇列已經存在任務,則需要執行緒池初始化完畢後,預先啟動執行緒。
建立新執行緒
使用ThreadFactory(執行緒工廠)建立新執行緒。如果沒有特別指定,則使用Executors.defaultThreadFactory()作為預設的執行緒工廠,該執行緒工廠所建立的執行緒都位於相同的執行緒組(ThreadGroup)中,執行緒的優先順序都是NORM_PRIORITY,執行緒守護狀態都為false。通過提供不同執行緒工廠的實現,你可以修改執行緒名、執行緒組、執行緒優先順序和守護狀態等等。
活躍時間
如果執行緒池中的執行緒數超過核心執行緒數,多出的執行緒如果空閒時間超出keepAliveTime(活躍時間)將會終止,回收不再活躍的執行緒。當有需要時,新的執行緒會重新建立。可以通過setKeepAliveTime(long time, TimeUnit unit)動態設定活躍時間。如果time設定為Long.MAX_VALUE,unit設定為TimeUnit.NANOSECONDS,那麼多餘的空閒執行緒將不會在關閉執行緒池之前回收。如果呼叫allowCoreThreadTimeOut(boolean value)傳入的value為true,那麼keepAliveTime將適用於核心執行緒,如果allowCoreThreadTimeOut為true且keepAliveTime不為0,核心執行緒的空閒時間超出活躍時間,核心執行緒也會被回收。
佇列
阻塞佇列(BlockingQueue)允許在獲取元素時陷入等待,直到有元素加入到佇列中。呼叫阻塞佇列方法時,有些方法不一定馬上返回,可能會在未來某個時刻達成某些條件時返回。阻塞佇列的方法伴隨四種形式:
- 丟擲異常。
- 返回特殊值,null或者false,具體視操作而定。
- 呼叫執行緒無限期陷入阻塞直到某些條件達成。
- 限定阻塞時長。
拋異常 | 特殊值 | 阻塞 | 超時 | |
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
檢查(獲取但不移除佇列頭部元素) | element() | peek() |
阻塞佇列不接受null元素,如果呼叫add、put、offer嘗試新增一個null元素,將會丟擲NullPointerException異常,當呼叫poll操作失敗時也會返回null。阻塞佇列可能有容量限制,無論何時都不能向佇列新增超過剩餘容量的元素,否則只能呼叫put方法陷入阻塞,直到有剩餘的空間可以容納元素。如果對佇列的容納空間沒有限制,則剩餘容量返回Integer.MAX_VALUE。阻塞佇列的實現一般用於生產者-消費者佇列的場景,此外阻塞佇列還實現了Collection介面,因此,佇列還可以使用remove(x)來移除元素。
阻塞佇列是執行緒安全的,所有排隊方法的實現都是用內部鎖或者其他併發控制手段來實現原子性的。然而,除非是特殊規定,否則大部分集合操作,如:addAll、containsAll、retainAll 、removeAll不一定要保證原子性。因此,可能出現在呼叫addAll(c)時,只新增c中一部分的元素就丟擲異常。阻塞佇列本質上並不支援關閉的操作,如:close或shutdown,當有需要讓佇列不再接受新元素。如果有這種需要或者特性更傾向於以來佇列的實現。一種常見的策略是生產者往佇列插入具有特殊標識的物件,當消費者使用物件時,會對特殊標識進行解釋。
注意,阻塞佇列允許多個生產者和消費者同時使用,如下:
class Producer implements Runnable { private final BlockingQueue queue; Producer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { queue.put(produce()); } } catch (InterruptedException ex) { ... handle ...} } Object produce() { ... } } class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { consume(queue.take()); } } catch (InterruptedException ex) { ... handle ...} } void consume(Object x) { ... } } class Setup { void main() { BlockingQueue q = new SomeQueueImplementation(); Producer p = new Producer(q); Consumer c1 = new Consumer(q); Consumer c2 = new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }
public interface BlockingQueue<E> extends Queue<E> { /** * 在不超過佇列容量的情況下插入一個元素將返回true,,如果佇列沒有多餘的空間丟擲 * IllegalStateException異常,當使用容量受限的佇列時最好使用offer。 * * @param e 待新增進佇列的元素 * @return 返回true代表元素加入佇列成功 */ boolean add(E e); /** * 相比add(E)如果佇列滿時插入元素不報錯,只是返回false。 * * @param e 待新增進佇列的元素 * @return 返回true代表元素加入佇列成功,佇列滿時無法插入返回false */ boolean offer(E e); /** * 將一個元素插入到佇列,如果有必要會等待佇列有多餘空間可以插入。如果呼叫 * put(E)的執行緒被中斷,將丟擲中斷異常InterruptedException * * @param e 待新增進佇列的元素 */ void put(E e) throws InterruptedException; /** * 相比offer(E)多了插入元素時陷入等待,如果等待期間佇列依舊 * 沒有多餘的空間容納元素,則返回false,如果等待期間能插入則返回true。 * 如果等待期間執行緒被中斷,則丟擲中斷異常InterruptedException。 * * @param e 待新增進佇列的元素 * @param timeout 等待時長 * @param unit 等待時長單位 */ boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; /** * 取出並刪除佇列的頭部元素,如果佇列為空,則會陷入等待,直到佇列有新的元素加入, * 如果等待期間執行緒被中斷,將丟擲中斷異常 * * @return 對頭元素 */ E take() throws InterruptedException; /** * 相比take()多了一個等待時長,如果佇列本身有元素,或者佇列原先有空但等待期間有元素 * 加入則返回頭部元素,否則佇列為空且等待期間沒有元素加入,則返回null。如果等待期間呼叫執行緒 * 被中斷,則丟擲InterruptedException異常。 */ E poll(long timeout, TimeUnit unit) throws InterruptedException; /** * 返回佇列理想狀況下可無阻塞容納元素的容量。注意:我們不能通過此方法判斷元素是否插入成功, * 因為可能存在別的執行緒插入或刪除佇列中的元素。 */ int remainingCapacity(); /** * 從佇列中移除指定的元素,如果佇列中存在一個或多個相同的元素,即:o.equal(e),則刪除並返回true。 */ boolean remove(Object o); /** * 如果佇列存在一個或多個相同的元素,即:o.equal(e),則返回true。 */ boolean contains(Object o); /** * 刪除此佇列中所有可用元素,並將它們移動到給定的集合c中。當我們把元素從原佇列取出時新增到集合c時, * 可能出現異常導致元素既不在原佇列,也不在集合中。佇列同樣實現了Collection介面,如果將原佇列當做 * 引數傳入將丟擲IllegalArgumentException異常 * @param c 將佇列元素傳輸到給定的集合c。 * @return 加入到集合c中元素的數量。 */ int drainTo(Collection<? super E> c); /** * 最多將maxElements個元素從佇列傳輸到給定集合,其他和drainTo(Collection<? super E> c)一樣。 */ int drainTo(Collection<? super E> c, int maxElements); }
任何阻塞佇列都可以用來獲取和儲存任務,如何使用佇列視當前執行緒池的大小而定:
- 如果工作執行緒的數量小於corePoolSize,那麼Executor更傾向於新增新執行緒執行而非讓任務排隊。
- 如果工作執行緒的數量大於等於corePoolSize,那麼Executor更傾向於把任務新增到佇列等待執行,而非建立新執行緒。
- 如果佇列已滿,且執行緒池內的執行緒數小於maximumPoolSize,Executor會建立一個新執行緒來執行任務,否則任務會被拒絕。
通常有三種排隊策略:
- 同步佇列(SynchronousQueue):如果有執行緒從同步佇列獲取任務,則移交給執行緒,否則持有任務,如果有新的任務嘗試入隊將返回失敗,可以根據入隊結果判斷是否要構造一個新執行緒。同步佇列可以避免當處理多個請求時內部依賴出現鎖定,直接交接任務要求對maximumPoolSize這一引數不做限制,即maximumPoolSize為Integer.MAX_VALUE,避免執行緒池拒絕提交任務。但如果執行緒池處理任務的速度不夠快,可能出現執行緒無限增長。
- 無界佇列(LinkedBlockingQueue):如果執行緒池核心工作執行緒都在執行任務時,新提交的任務將在佇列中等待。如果任務提交速度過快而執行任務的速度又慢,將導致佇列中的任務無限增長。
- 有界佇列(ArrayBlockingQueue):當與有限的maximumPoolSize配合使用時,有界佇列可以防止資源耗盡,但如何設定有界佇列的大小是一個很難的問題。如果佇列過大而maximumPoolSize過小,可以減少CPU的使用、作業系統資源和上下文切換的開銷,這有可能導致低吞吐量。如果佇列過小而maximumPoolSize過大,這會使得CPU十分繁忙,甚至出現巨大的排程開銷,同樣也會降低吞吐量。
拒絕任務
如果執行緒池關閉後有新任務提交、或者在任務佇列已滿的情況下,執行緒池到達最大執行緒數且所有執行緒都在執行任務,將呼叫RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)拒絕任務。預設提供四種預定義拒絕策略:
- ThreadPoolExecutor.AbortPolicy:預設情況下使用的策略,丟擲RejectedExecutionException異常。
- ThreadPoolExecutor.CallerRunsPolicy:使用呼叫執行緒執行任務,這種機制可以降低任務提交的速度。
- ThreadPoolExecutor.DiscardPolicy:將無法入隊也不能執行的任務直接丟棄。
- ThreadPoolExecutor.DiscardOldestPolicy:如果執行緒池未關閉,則獲取並丟棄佇列的頭部元素,再嘗試用執行緒池執行任務,這一策略可能會失敗,如果失敗則重複之前的步驟。
除了上述四種步驟,我們也可以自定義拒絕策略。
鉤子函式
ThreadPoolExecutor提供了可重寫函式beforeExecute(java.lang.Thread, java.lang.Runnable)、afterExecute(java.lang.Runnable, java.lang.Throwable),分別允許我們在執行任務前和執行任務後做一些操作,這些方法可以控制執行環境,例如:初始化ThreadLocals、收集統計資訊、新增日誌等等。此外,也可以重寫terminated()方法,當執行緒池完全終止後會呼叫此方法。
如果鉤子函式或者回撥函式丟擲異常,工作執行緒可能會終止。
佇列維護
ThreadPoolExecutor提供了getQueue()允許外部獲取佇列進行監控和除錯,但不管出於什麼目的儘量少使用此方法。此外ThreadPoolExecutor還提供了remove(java.lang.Runnable) 和purge()用於刪除任務,purge()可以取消大量處於排隊等待的任務。
銷燬
當一個執行緒池不再有引用指向,且執行緒池內沒有存活執行緒將會自動關閉。如果你期望一個未手動呼叫shutdown()方法的執行緒池會被回收,你要設定合理的執行緒存活時間(keep-alive times)、設定核心執行緒數為0,或者設定allowCoreThreadTimeOut為true,當核心執行緒空閒時間超過存活時間將被回收,當執行緒池沒有引用指向,且無存活執行緒,就會被自動關閉並回收。
原始碼解析
ThreadPoolExecutor的ctl變數型別為AtomicInteger,這個數值有32位,包含兩個部分:
- runState(執行狀態):執行緒池是否處於執行中、是否已關閉等等。
- workerCount(工作執行緒數):執行緒池當前有多少個存活(忙碌或空閒)的執行緒。
為了將執行狀態和工作執行緒數放在一個int欄位,我們劃分前3位儲存執行狀態,後29位儲存存活執行緒數量(2^29)-1(約5億)。未來有可能調整ctl為AtomicLong型別,這可能需要調整移位和掩碼,但如果使用AtomicInteger,ThreadPoolExecutor的程式碼會更簡單也更高效一些。
workerCount是執行緒池中還存活的執行緒數,該值有時候可能會短暫不同於池內實際的存活執行緒數。當需要增加工作執行緒時,會先用CAS的方式對workerCount+1,然後才向ThreadFactory申請建立一個執行緒。
runState為執行緒池提供了生命週期控制,有以下幾種狀態:
- RUNNING:允許接受新任務和處理佇列中任務。
- SHUTDOWN:不接受新任務,但處理佇列中任務。
- STOP:不接受新任務,不處理佇列中任務,同時嘗試中斷正在執行的任務。
- TIDYING:所有任務都終止,workerCount為0,執行緒池狀態過度到TIDYING,將要執行terminated()鉤子函式。
- TERMINATED:terminated()函式執行完畢。
下面,我們來看看執行緒池狀態的轉換:
- RUNNING->SHUTDOWN:呼叫shutdown()。
- (RUNNING or SHUTDOWN)->STOP:呼叫shutdownNow()。
- SHUTDOWN->TIDYING:當執行緒池不再有存活執行緒且佇列為空。
- STOP->TIDYING:當執行緒池不再有存活執行緒。
- TIDYING->TERMINATED:呼叫terminated()。
檢測執行緒池的狀態從SHUTDOWN過度到TIDYING並非易事,因為在SHUTDOWN狀態下,佇列可能從非空變為空,即仍然有存活的執行緒處理佇列中的任務。只有workerCount為0且佇列為空,才能結束執行緒池。
public class ThreadPoolExecutor extends AbstractExecutorService { /** * 型別為AtomicInteger的ctl可以保證執行緒安全,該數值分兩個部分: * 前3位為runState代表執行緒池當前的狀態:RUNNING~TERMINATED, * 後29位為workerCount代表執行緒池記憶體活執行緒數量。 */ private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); /** * Integer.SIZE=32,COUNT_BITS=32-3=29,用COUNT_BITS來劃分 * runState和workerCount。 */ private static final int COUNT_BITS = Integer.SIZE - 3; /** * 1 << COUNT_BITS = 0010 0000 0000 0000 0000 0000 0000 0000 * COUNT_MASK = (1 << COUNT_BITS) - 1 = 0001 1111 1111 1111 1111 1111 1111 1111 * ~COUNT_MASK = 1110 0000 0000 0000 0000 0000 0000 0000 * COUNT_MASK可以幫助我們計算執行緒池當前的runState和workerCount。 * 假設ctl的值為:0000 0000 0000 0000 0000 0000 0000 0011 * 呼叫runStateOf(ctl.get()),將做ctl.get() & ~COUNT_MASK運算: * 0000 0000 0000 0000 0000 0000 0000 0011 * & 1110 0000 0000 0000 0000 0000 0000 0000 * = 0000 0000 0000 0000 0000 0000 0000 0000 * 由此我們可以得到,執行緒池當前狀態為0,即SHUTDOWN。 * 呼叫workerCountOf(ctl.get()),將做ctl.get() & COUNT_MASK運算: * 0000 0000 0000 0000 0000 0000 0000 0011 * & 0001 1111 1111 1111 1111 1111 1111 1111 * = 0000 0000 0000 0000 0000 0000 0000 0011 * 由此我們可以得到,執行緒池還有3個存活的執行緒。 */ private static final int COUNT_MASK = (1 << COUNT_BITS) - 1; /** * -1的二進位制表示為:1111 1111 1111 1111 1111 1111 1111 1111, * 左移29位為:1110 0000 0000 0000 0000 0000 0000 0000 */ private static final int RUNNING = -1 << COUNT_BITS; /** * 0的二進位制表示為:0000 0000 0000 0000 0000 0000 0000 0000, * 左移29位和原先沒有變化。 */ private static final int SHUTDOWN = 0 << COUNT_BITS; /** * 0的二進位制表示為:0000 0000 0000 0000 0000 0000 0000 0001, * 左移29位為:0010 0000 0000 0000 0000 0000 0000 0000 */ private static final int STOP = 1 << COUNT_BITS; /** * 2的二進位制表示為:0000 0000 0000 0000 0000 0000 0000 0010, * 左移29位為:0100 0000 0000 0000 0000 0000 0000 0000 */ private static final int TIDYING = 2 << COUNT_BITS; /** * 3的二進位制表示為:0000 0000 0000 0000 0000 0000 0000 0011, * 左移29位為:0110 0000 0000 0000 0000 0000 0000 0000 */ private static final int TERMINATED = 3 << COUNT_BITS; private static int runStateOf(int c) { return c & ~COUNT_MASK; } private static int workerCountOf(int c) { return c & COUNT_MASK; } /** * 根據runState和workerCount生成ctl,比如初始化執行緒池時, * ctl = new AtomicInteger(ctlOf(RUNNING, 0)),代表執行緒池 * 的狀態為RUNNING,存活執行緒數量為0。 * @param rs 執行緒池狀態 * @param wc 存活執行緒數量 * @return */ private static int ctlOf(int rs, int wc) { return rs | wc; } /** * 通過位運算在進行一些狀態的判斷時,我們不需要解析ctl的執行狀態, * 假設當前ctl:1110 0000 0000 0000 0000 0000 0000 0011, * 我們要判斷執行緒池狀態是否小於STOP,由於ctl開頭為1110,以補碼的 * 方式來計算,ctl的值必然為負,STOP開頭為0010,以補碼方式計算為正數, * 所以ctl必然小於STOP。 * ctl的佈局還能保證workerCount永遠不會為負數。 */ private static boolean runStateLessThan(int c, int s) { return c < s; } /** * 判斷執行緒池至少處於某個狀態,假設執行緒池現在佇列為空且無任何存活執行緒, * 所以能保證執行緒池處於TIDYING狀態,如果s我們傳入STOP,TIDYING的開頭 * 為0100,STOP的開頭為0010,TIDYING>STOP,所以我們能知道,執行緒池至少 * 處於STOP以上包含STOP的狀態。 */ private static boolean runStateAtLeast(int c, int s) { return c >= s; } //執行緒池是否處於RUNNING狀態 private static boolean isRunning(int c) { return c < SHUTDOWN; } //CAS增加worker數量 private boolean compareAndIncrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect + 1); } //CAS減少worker數量 private boolean compareAndDecrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect - 1); } }
我們知道AbstractExecutorService.submit(...)方法最終會呼叫execute(Runnable command)方法,而AbstractExecutorService類中並沒有實現execute(Runnable command)方法,它將execute(Runnable command)的實現交由子類。那麼我們來看看ThreadPoolExecutor又是如何實現execute(Runnable command)方法呢?當一個任務提交到執行緒池,它的執行流程又是如何呢?來看下面的程式碼註釋:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); //如果工作執行緒的數量小於核心執行緒的數量,則嘗試增加工作執行緒 if (workerCountOf(c) < corePoolSize) {//<1> /* * 如果成功增加工作執行緒,工作執行緒會執行我們提交的任務,我們就可以安心退出, * 但執行緒池可以併發提交任務,可能存在在<1>處時工作執行緒小於核心執行緒數, * 執行<2>處的addWorker(Runnable firstTask, boolean core)時, * 其他執行緒先當前執行緒提交任務並增加工作執行緒,執行緒池內工作執行緒數超過核心執行緒數, * 當前執行緒增加工作執行緒失敗,不能直接退出。 * 注:addWorker(Runnable firstTask, boolean core),core為true * 代表增加核心執行緒,會將任務作為firstTask傳入;而false代表增加非核心執行緒, * 如果傳入firstTask為null,則代表讓工作執行緒去佇列中拉取任務。 */ if (addWorker(command, true))//<2> return; //如果<2>處增加工作執行緒失敗,則重新獲取ctl的值。 c = ctl.get(); } //判斷執行緒池是否處於執行中,且任務可以入隊成功,如果兩者成立,則進入<3>分支 if (isRunning(c) && workQueue.offer(command)) {//<3> int recheck = ctl.get(); /* * 重新獲取ctl,因為可能在進入<3>分支的時候,執行緒池被關閉, * 所以要重新判斷執行緒池狀態,如果執行緒池不是處於執行狀態,且 * 任務成功被移除,則進入<4>分支,拒絕任務。 */ if (!isRunning(recheck) && remove(command))//<4> reject(command); /* * 為什麼這裡要判斷工作執行緒數量是否為0?因為如果設定allowCoreThreadTimeOut * 為true的話,核心執行緒是可以為0的,可能程式碼執行到<3>處workQueue.offer(command)之前, * 即任務還未入隊,工作執行緒數量已經為0了,所以這裡要重新根據ctl判斷工作執行緒是否為0, * 如果為0得再增加非核心執行緒去佇列拉取並執行任務。 */ else if (workerCountOf(recheck) == 0) addWorker(null, false); } /* * 如果沒有進入<3>分支,而到達<5>分支,一般分兩種情況: * 1.執行緒池被關閉,<3>處isRunning(c)為false,此時呼叫<5>處的 * addWorker(...)必然返回false,然後執行拒絕策略。 * 2.執行緒池處於執行狀態,<3>處isRunning(c)為true,但佇列已滿 * workQueue.offer(command)返回false,入隊失敗。 * 這時候應該嘗試建立非核心工作執行緒執行任務,如果工作執行緒數量沒到達最大執行緒數, * 則建立執行緒並執行任務,如果工作執行緒到達最大執行緒數,則addWorker(...)返回 * false,執行拒絕策略。 */ else if (!addWorker(command, false))//<5> reject(command); }
從ThreadPoolExecutor.execute(Runnable command)的實現,我們可以知道addWorker(Runnable firstTask, boolean core)方法是至關重要的,它決定了是否將任務新增進執行緒池執行。下面,我們再來看看addWorker(Runnable firstTask, boolean core)方法:
/** * 此方法會根據執行緒池當前的執行狀態、執行緒池所設定的邊界(核心執行緒數和最大執行緒數)。 * 如果執行緒池允許建立執行緒執行任務,則建立執行緒執行firstTask並相應調整工作執行緒的數量。 * 如果執行緒池狀態處於已停止(STOP)、關閉(SHUTDOWN)則會返回false。如果向執行緒工 * 廠請求建立執行緒失敗,也會返回false。執行緒建立失敗分兩種情況,一種是執行緒工廠返回null, * 或者執行Thread.start()時出現異常(通常為OOM異常)。 * * @param firstTask:新執行緒首要執行任務,如果沒有則傳入null。當工作執行緒數少於核心 執行緒數,執行緒池總是建立一個新執行緒來執行firstTask。 * @param core:根據core為true或者false,決定是以核心執行緒數或者最大執行緒數作為界限, 判斷當前執行緒池的工作執行緒池是否小於界限,如果小於則允許建立執行緒。 * @return 如果成功新增工作執行緒則返回true。 */ private boolean addWorker(Runnable firstTask, boolean core) { retry: for (int c = ctl.get(); ; ) {//<1> // Check if queue empty only if necessary. /* * 在這個地方addWorker(...)會返回false,即新增工作執行緒失敗, * 我們來看看是什麼情況下會進入這個分支: * runStateAtLeast(c, SHUTDOWN)代表執行緒池執行狀態至少處於 * SHUTDOWN,如果執行緒池還處於RUNNING執行狀態,此方法不會立即 * 返回失敗。所以我們知道,要進入此分支,首要條件就是執行狀態大於 * 等於SHUTDOWN。 * 之後如果runStateAtLeast(c, STOP)、firstTask != null、 * workQueue.isEmpty())這三個條件其一為true,則新增執行緒失敗。 * 首先是runStateAtLeast(c, STOP),如果執行緒池當前處於STOP * 狀態,這時候既不接受新任務,也不處理佇列裡的任務,所以不管 * firstTask是否為null,都返回false。 * 如果runStateAtLeast(c, STOP)為false,那執行狀態只能是 * SHUTDOWN,SHUTDOWN狀態下會處理佇列裡的任務,但不再接受新 * 任務,所以firstTask不為null,也直接返回false。 * 如果執行狀態既處於SHUTDOWN、firstTask也會空,且任務佇列也 * 為空,則毫無必要增加工作執行緒,也直接返回false。 * 所以總結一下有兩種情況不會進入此分支: * 1.執行緒池處於RUNNING狀態的時候。 * 2.執行緒池處於SHUTDOWN,但firstTask為空佇列不為空時。 */ if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty())) return false; for (; ; ) {//<2> //根據core判斷工作執行緒的上限,如果大於上限則返回false。 if (workerCountOf(c) >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK)) return false; /* * 如果用CAS的方式成功增加工作執行緒的數量,則用break retry的方式 * 結束了retry對應的外層迴圈(即<1>處for迴圈),而不是break所在 * 的本層迴圈(即<2>處迴圈),程式碼會從<3>處開始執行。 */ if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl /* * 如果上面用CAS的方式增加工作執行緒失敗,則會重新判斷執行緒池當前 * 狀態是否至少處於SHUTDOWN,如果執行緒池已關閉,程式碼會跳到retry * 處重新執行<1>處的for迴圈。如果執行緒池仍然處於RUNNING狀態,則 * 重複執行<2>處的迴圈。 */ if (runStateAtLeast(c, SHUTDOWN)) continue retry; // else CAS failed due to workerCount change; retry inner loop } } //<3> boolean workerStarted = false;//如果工作執行緒啟動成功,則賦值為true boolean workerAdded = false;//如果工作執行緒新增成功則賦值為true Worker w = null; try { //建立一個Worker物件,Worker物件會向執行緒工廠申請建立一個執行緒 w = new Worker(firstTask); final Thread t = w.thread; //如果執行緒工廠建立的Thread物件不為null,則進入此分支 if (t != null) { /* * 這裡用可重入鎖鎖住try模組程式碼,因為要將之前建立好的 * w物件放進workers集合。 * 注:重入鎖ReentrantLock的概念筆者會在以後的文章裡 * 單獨介紹,這裡先簡單理解,可重入鎖就是禁止其他執行緒同時 * 訪問mainLock.lock()到mainLock.unlock()之間的程式碼, * 和synchronized有些類似。 */ 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 c = ctl.get(); /* * 重新獲取ctl,判斷執行緒池當前是否處於執行狀態,或小於STOP狀態, * 即執行緒池處於RUNNING或SHUTDOWN,如果處於RUNNING則直接進入分支, * 如果處於SHUTDOWN且首要執行任務為空,代表可能要啟動一個工作執行緒 * 來執行佇列中的任務。 */ if (isRunning(c) || (runStateLessThan(c, STOP) && firstTask == null)) { /* * 判斷執行緒是否已經啟動,如果使用的是Executors.DefaultThreadFactory * 預設的執行緒工廠,正常來說建立出來的Thread物件都是執行緒未啟動的,即:尚未 * 呼叫Thread.start()。但ThreadPoolExecutor允許我們傳入定製化的執行緒 * 工廠,所以會存線上程工廠建立出Thread物件,但Thread物件已呼叫過start() * 方法的可能。 */ if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //將建立好的worker新增進集合workers。 workers.add(w); //更新歷史上最大的工作執行緒數,即workers.size()。 int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; //將worker新增進workers後,更新workerAdded的值為true。 workerAdded = true; } } finally { mainLock.unlock(); } //如果worker成功加入集合,則啟動執行緒,並更新workerStarted為true。 if (workerAdded) { t.start(); workerStarted = true; } } } finally { /* * 如果worker沒有啟動,代表worker沒有加入到workers集合, * 可能執行緒池狀態>=STOP,則需要執行新增工作執行緒失敗操作。 */ if (!workerStarted) addWorkerFailed(w); } //返回工作執行緒是否啟動成功 return workerStarted; }