上面這段程式碼一直在用,面試的時候也經常被問到,卻從未深究過,不知道執行緒池到底是怎麼回事,今天看看原始碼,一探其究竟
執行緒池主要控制的狀態是ctl,它是一個原子的整數,其包含兩個概念欄位:
- workerCount:有效的執行緒數量
- runState:執行緒池的狀態
為了在一個整型值裡面包含這兩個欄位,我們限制workerCount最多2的29次方減1
runState的值有這樣幾種:
- RUNNING: 接受新的任務,並處理佇列中的任務
- SHUTDOWN:不接受新的任務,繼續處理佇列中的任務
- STOP: 不接受新的任務,也不處理佇列中的任務,並且中斷正在處理的任務
- TIDYING: 所有任務都結束了,workerCount是0,通過呼叫terminated()方法轉換狀態
- TERMINATED:terminated()方法已經完成
狀態之間的轉換時這樣的:
RUNNING -> SHUTDOWN
呼叫shutdown()方法,或者隱式的呼叫finalize()方法
(RUNNING or SHUTDOWN) -> STOP
呼叫shoutdownNow()方法
SHUTDOWN -> TIDYING
當佇列和池都是空的時候
STOP -> TIDYING
當池是空的時候
TIDYING -> TERMINATED
當terminated()方法呼叫完成時
Integer.SIZE=31
Integer.SIZE - 3 = 29
所以,COUNT_BITS = 29
高3位儲存runState
接下來看最複雜的那個構造方法
引數詳解
- corePoolSize:保持在池中的執行緒數(PS:即使它們處於空閒狀態)
- maximumPoolSize:池中允許的最大執行緒數
- keepAliveTime:當執行緒數超過核心執行緒數的時候,超出的執行緒的最大生存時間
- unit:keepAliveTime的單位
- workQueue:維護待處理的任務的佇列
- threadFactory:建立執行緒的工廠
1、如果正在執行的執行緒數少於核心執行緒數,則新建一個執行緒去執行這個任務
2、如果工作佇列沒有滿,則放到工作佇列中
3、如果工作佇列已滿(PS:workQueue.offer(command)返回false),則再新建執行緒
4、若執行緒數已經達到最大執行緒數則reject(command)
在前面execute方法中,有3處呼叫了addWork()方法
第一處,如果當前有效執行緒數少於核心執行緒數,則呼叫之,此時執行緒數的邊界是核心執行緒數
第二處,如果當前有效執行緒數超過核心執行緒數,並且將新任務放到佇列中,此時有效執行緒數是0,則建立一個新執行緒
第三處,如果當前有效執行緒數超過核心執行緒數,並且佇列已經放滿了,並且有效執行緒數小於最大執行緒數,此時呼叫以建立新執行緒,
當前的有效執行緒都在works裡面,而works裡面放的是Worker物件,接下來看Worker
前一步中,執行緒的start()方法,執行緒執行的時候執行run()方法,而Worker繼承Runnable並重新run()方法,run()方法又呼叫外部的runWorker()方法,所以,線上程池中新建立的執行緒執行時呼叫的是runWorker()方法
runWorker()方法迴圈的從佇列中取出任務並執行它。也就是說,池中所有的執行緒都是在建立的時候如果傳進來新任務,則先執行新任務,然後迴圈從佇列中取出任務並執行
接下來,看Executors中常見的幾種執行緒池的區別
可以看到
newSingleThreadExecutor:核心執行緒數和最大執行緒數都是1,佇列是LinkedBlockingQueue
newFixedThreadPool:核心執行緒數和最大執行緒數相等,超過核心執行緒數的空閒執行緒生存時間是0,佇列是LinkedBlockingQueue
newCachedThreadPool:核心執行緒數是0,最大執行緒數是Integer.MAX_VALUE,空閒執行緒生存時間是1min,佇列是SynchronousQueue
舉例說明
栗子1
假設,newFixedThreadPool的時候固定執行緒是2,所有,最大執行緒和核心執行緒都是2,而它的工作佇列用的是LinkedBlockingQueue。
當第1個任務提交過來,新建立一個執行緒並執行任務,此時執行緒池中有1個執行緒;
當第2個任務提交過來,再新建一下執行緒並執行任務,此時執行緒池中有2個執行緒;
當第3個任務提交過來,放入工作佇列中,然後由執行緒池中的兩個執行緒輪詢佇列處理
當第n個任務提交過來,仍然放入工作佇列
栗子2
假設,newCachedThreadPool,需要注意的是,它的工作佇列是SynchronousQueue,這種佇列的特性是一個執行緒向佇列中放元素的時候必須同時另一個執行緒從佇列中取出元素,否則是放不進去的
當第1個任務提交過來,執行緒池中的執行緒數為0,於是,新建一個執行緒,並執行任務,此時,執行緒池中有1個執行緒;
當第2個任務提交過來,由於無法放到佇列中,於是再建立一個執行緒,此時執行緒池中有2個執行緒;
當第3個任務提交過來,若前兩個執行緒有一個處理完了,這個時候由於是迴圈從佇列中取,所有可以放到工作佇列中