執行緒池學習
以下所有內容以及原始碼分析都是基於JDK1.8的,請知悉。
我寫部落格就真的比較沒有順序了,這可能跟我的學習方式有關,我自己也覺得這樣挺不好的,但是沒辦法說服自己去改變,所以也只能這樣想到什麼學什麼了。
池化技術真的是一門在我看來非常牛逼的技術,因為它做到了在有限資源內實現了資源利用的最大化,這讓我想到了一門課程,那就是運籌學,當時在上運籌學的時候就經常做這種類似的問題。
言歸正傳吧,我接下來會進行一次執行緒池方面知識點的學習,也會記錄下來分享給大家。
執行緒池的內容當中有涉及到AQS同步器的知識點,如果對AQS同步器知識點感覺有點薄弱,可以去看我的上一篇文章。
執行緒池的優勢
既然說到執行緒池了,而且大多數的大牛也都會建議我們使用池化技術來管理一些資源,那執行緒池肯定也是有它的好處的,要不然怎麼會那麼出名並且讓大家使用呢?
我們就來看看它究竟有什麼優勢?
-
資源可控性:使用執行緒池可以避免建立大量執行緒而導致記憶體的消耗
-
提高響應速度:執行緒池地建立實際上是很消耗時間和效能的,由執行緒池建立好有任務就執行,提升響應速度。
-
便於管理:池化技術最突出的一個特點就是可以幫助我們對池子裡的資源進行管理。由執行緒池統一分配和管理。
執行緒池的建立
我們要用執行緒池來統一分配和管理我們的執行緒,那首先我們要建立一個執行緒池出來,還是有很多大牛已經幫我們寫好了很多方面的程式碼的,Executors的工廠方法就給我們提供了建立多種不同執行緒池的方法。因為這個類只是一個建立物件的工廠,並沒有涉及到很多的具體實現,所以我不會過於詳細地去說明。
老規矩,還是直接上程式碼吧。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
複製程式碼
這裡也就舉出一個方法的例子來進行之後的講解吧,我們可以看出,Executors只是個工廠而已,方法也只是來例項化不同的物件,實際上例項化出來的關鍵類就是ThreadPoolExecutor
。現在我們就先來簡單地對ThreadPoolExecutor
建構函式內的每個引數進行解釋一下吧。
-
corePoolSize(核心執行緒池大小):當提交一個任務到執行緒池時,執行緒池會建立一個執行緒來執行任務,即使其他空閒的基本執行緒能夠執行新任務也會建立執行緒,當任務數大於核心執行緒數的時候就不會再建立。在這裡要注意一點,執行緒池剛建立的時候,其中並沒有建立任何執行緒,而是等任務來才去建立執行緒,除非呼叫了
prestartAllCoreThreads()
或者prestartCoreThread()
方法 ,這樣才會預先建立好corePoolSize
個執行緒或者一個執行緒。 -
maximumPoolSize(執行緒池最大執行緒數):執行緒池允許建立的最大執行緒數,如果佇列滿了,並且已建立的執行緒數小於最大執行緒數,則執行緒池會再建立新的執行緒執行任務。值得注意的是,如果使用了無界佇列,此引數就沒有意義了。
-
keepAliveTime(執行緒活動保持時間):此引數預設線上程數大於
corePoolSize
的情況下才會起作用, 當執行緒的空閒時間達到keepAliveTime
的時候就會終止,直至執行緒數目小於corePoolSize
。不過如果呼叫了allowCoreThreadTimeOut
方法,則當執行緒數目小於corePoolSize
的時候也會起作用. -
unit(keelAliveTime的時間單位):keelAliveTime的時間單位,一共有7種,在這裡就不列舉了。
-
workQueue(阻塞佇列):阻塞佇列,用來儲存等待執行的任務,這個引數也是非常重要的,在這裡簡單介紹一下幾個阻塞佇列。
-
ArrayBlockingQueue:這是一個基於陣列結構的有界阻塞佇列,此佇列按照FIFO的原則對元素進行排序。
-
LinkedBlockingQueue:一個基於連結串列結構的阻塞佇列,此佇列按照FIFO排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法
Executors.newFixedThreadPool()
就是使用了這個佇列。 -
SynchronousQueue:一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態。吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法
Executors.newCachedThreadPool()
就使用了這個佇列。 -
PriorityBlockingQueue:一個具有優先順序的無阻塞佇列。
-
-
handler(飽和策略);當執行緒池和佇列都滿了,說明執行緒池已經處於飽和狀態了,那麼必須採取一種策略來處理還在提交過來的新任務。這個飽和策略預設情況下是
AbortPolicy
,表示無法處理新任務時丟擲異常。共有四種飽和策略提供,當然我們也可以選擇自己實現飽和策略。-
AbortPolicy:直接丟棄並且丟擲
RejectedExecutionException
異常 -
CallerRunsPolicy:只用呼叫者所線上程來執行任務。
-
DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。
-
DiscardPolicy:丟棄任務並且不丟擲異常。
-
執行緒池的執行流程就用參考資料裡的圖介紹一下了,具體我們還是通過程式碼去講解。
在上面我們簡單的講解了一下Executors
這個工廠類裡的工廠方法,並且講述了一下建立執行緒池的一些引數以及它們的作用,當然上面的講解並不是很深入,因為想要弄懂的話是需要持續地花時間去看去理解的,而博主自己也還是沒有完全弄懂,不過博主的學習方法是先學了個大概,再回頭來看看之前的知識點,可能會更加好理解,所以我們接著往下面講吧。
ThreadPoolExecutor原始碼分析
在上面我們就發現了,Executors
的工廠方法主要就返回了ThreadPoolExecutor
物件,至於另一個在這裡暫時不講,也就是說,要學習執行緒池,其實關鍵的還是得學會分析ThreadPoolExecutor
這個物件裡面的原始碼,我們接下來就會對ThreadPoolExecutor
裡的關鍵程式碼進行分析。
AtomicInteger ctl
ctl
是主要的控制狀態,是一個複合型別的變數,其中包括了兩個概念。
-
workerCount:表示有效的執行緒數目
-
runState:執行緒池裡執行緒的執行狀態
我們來分析一下跟ctl
有關的一些原始碼吧,直接上程式碼
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//用來表示執行緒池數量的位數,很明顯是29,Integer.SIZE=32
private static final int COUNT_BITS = Integer.SIZE - 3;
//執行緒池最大數量,2^29 - 1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//我們可以看出有5種runState狀態,證明至少需要3位來表示runState狀態
//所以高三位就是表示runState了
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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
//用於存放執行緒任務的阻塞佇列
private final BlockingQueue<Runnable> workQueue;
//重入鎖
private final ReentrantLock mainLock = new ReentrantLock();
//執行緒池當中的執行緒集合,只有當擁有mainLock鎖的時候,才可以進行訪問
private final HashSet<Worker> workers = new HashSet<Worker>();
//等待條件支援終止
private final Condition termination = mainLock.newCondition();
//建立新執行緒的執行緒工廠
private volatile ThreadFactory threadFactory;
//飽和策略
private volatile RejectedExecutionHandler handler;
複製程式碼
-
CAPACITY
在這裡我們講一下這個執行緒池最大數量的計算吧,因為這裡涉及到原始碼以及位移之類的操作,我感覺大多數人都還是不太會這個,因為我一開始看的時候也是不太會的。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
複製程式碼
從程式碼我們可以看出,是需要1往左移29位
,然後再減去1,那個1往左移29位
是怎麼計算的呢?
1 << COUNT_BITS
1的32位2進位制是
00000000 00000000 00000000 00000001
左移29位的話就是
00100000 00000000 00000000 00000000
再進行減一的操作
000 11111 11111111 11111111 11111111
也就是說執行緒池最大數目就是
000 11111 11111111 11111111 11111111
複製程式碼
2.runState
正數的原碼、反碼、補碼都是一樣的 在計算機底層,是用補碼來表示的
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;
複製程式碼
- RUNNING
可以接受新任務並且處理已經在阻塞佇列的任務 高3位全部是1的話,就是RUNNING狀態
-1 << COUNT_BITS
這裡是-1往左移29位,稍微有點不一樣,-1的話需要我們自己算出補碼來
-1的原碼
10000000 00000000 00000000 00000001
-1的反碼,負數的反碼是將原碼除符號位以外全部取反
11111111 11111111 11111111 11111110
-1的補碼,負數的補碼就是將反碼+1
11111111 11111111 11111111 11111111
關鍵了,往左移29位,所以高3位全是1就是RUNNING狀態
111 00000 00000000 00000000 00000000
複製程式碼
- SHUTDOWN
不接受新任務,但是處理已經在阻塞佇列的任務 高3位全是0,就是SHUTDOWN狀態
0 << COUNT_BITS
0的表示
00000000 00000000 00000000 00000000
往左移29位
00000000 00000000 00000000 00000000
複製程式碼
- STOP
不接受新任務,也不處理阻塞佇列裡的任務,並且會中斷正在處理的任務 所以高3位是001,就是STOP狀態
1 << COUNT_BITS
1的表示
00000000 00000000 00000000 00000001
往左移29位
00100000 00000000 00000000 00000000
複製程式碼
- TIDYING
所有任務都被中止,workerCount是0,執行緒狀態轉化為TIDYING並且呼叫terminated()鉤子方法 所以高3位是010,就是TIDYING狀態
2 << COUNT_BITS
2的32位2進位制
00000000 00000000 00000000 00000010
往左移29位
01000000 00000000 00000000 00000000
複製程式碼
- TERMINATED
terminated()鉤子方法已經完成 所以高3位是110,就是TERMINATED狀態
3 << COUNT_BITS
3的32位2進位制
00000000 00000000 00000000 00000011
往左移29位
11000000 00000000 00000000 00000000
複製程式碼
3.部分方法介紹
- runStateOf(int c)
實時獲取runState的方法
private static int runStateOf(int c) { return c & ~CAPACITY; }
複製程式碼
~CAPACITY
~是按位取反的意思
&是按位與的意思
而CAPACITY是,高位3個0,低29位都是1,所以是
000 11111 11111111 11111111 11111111
取反的話就是
111 00000 00000000 00000000 00000000
傳進來的c引數與取反的CAPACITY進行按位與操作
1、低位29個0進行按位與,還是29個0
2、高位3個1,既保持c引數的高3位
既高位保持原樣,低29位都是0,這也就獲得了執行緒池的執行狀態runState
複製程式碼
- workerCountOf(int c)
獲取執行緒池的當前有效執行緒數目
private static int workerCountOf(int c) { return c & CAPACITY; }
複製程式碼
CAPACITY的32位2進位制是
000 11111 11111111 11111111 11111111
用入參c跟CAPACITY進行按位與操作
1、低29位都是1,所以保留c的低29位,也就是有效執行緒數
2、高3位都是0,所以c的高3位也是0
這樣獲取出來的便是workerCount的值
複製程式碼
- ctlOf(int rs, int wc)
原子整型變數ctl的初始化方法
//結合這幾句程式碼來看
private static final int RUNNING = -1 << COUNT_BITS;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
複製程式碼
RUNNING是
111 00000 00000000 00000000 00000000
ctlOf是將rs和wc進行按位或的操作
初始化的時候是將RUNNING和0進行按位或
0的32位2進位制是
00000000 00000000 00000000 00000000
所以初始化的ctl是
111 00000 00000000 00000000 00000000
複製程式碼
核心方法原始碼分析
- execute(Runnable command)方法
public void execute(Runnable command) {
//需要執行的任務command為空,丟擲空指標異常
if (command == null) // 1
throw new NullPointerException();
/*
*執行的流程實際上分為三步
*1、如果執行的執行緒小於corePoolSize,以使用者給定的Runable物件新開一個執行緒去執行
* 並且執行addWorker方法會以原子性操作去檢查runState和workerCount,以防止當返回false的
* 時候新增了不應該新增的執行緒
*2、 如果任務能夠成功新增到佇列當中,我們仍需要對新增的執行緒進行雙重檢查,有可能新增的執行緒在前
* 一次檢查時已經死亡,又或者在進入該方法的時候執行緒池關閉了。所以我們需要複查狀態,並有有必
* 要的話需要在停止時回滾入列操作,或者在沒有執行緒的時候新開一個執行緒
*3、如果任務無法入列,那我們需要嘗試新增一個執行緒,如果新建執行緒失敗了,我們就知道執行緒可能關閉了
* 或者飽和了,就需要拒絕這個任務
*
*/
//獲取執行緒池的控制狀態
int c = ctl.get(); // 2
//通過workCountOf方法算workerCount值,小於corePoolSize
if (workerCountOf(c) < corePoolSize) {
//新增任務到worker集合當中
if (addWorker(command, true))
return; //成功返回
//失敗的話再次獲取執行緒池的控制狀態
c = ctl.get();
}
/*
*判斷執行緒池是否正處於RUNNING狀態
*是的話新增Runnable物件到workQueue佇列當中
*/
if (isRunning(c) && workQueue.offer(command)) { // 3
//再次獲取執行緒池的狀態
int recheck = ctl.get();
//再次檢查狀態
//執行緒池不處於RUNNING狀態,將任務從workQueue佇列中移除
if (! isRunning(recheck) && remove(command))
//拒絕任務
reject(command);
//workerCount等於0
else if (workerCountOf(recheck) == 0) // 4
//新增worker
addWorker(null, false);
}
//加入阻塞佇列失敗,則嘗試以執行緒池最大執行緒數新開執行緒去執行該任務
else if (!addWorker(command, false)) // 5
//執行失敗則拒絕任務
reject(command);
}
複製程式碼
我們來說一下上面這個程式碼的流程:
1、首先判斷任務是否為空,空則丟擲空指標異常 2、不為空則獲取執行緒池控制狀態,判斷小於corePoolSize,新增到worker集合當中執行,
- 如成功,則返回
- 失敗的話再接著獲取執行緒池控制狀態,因為只有狀態變了才會失敗,所以重新獲取 3、判斷執行緒池是否處於執行狀態,是的話則新增command到阻塞佇列,加入時也會再次獲取狀態並且檢測 狀態是否不處於執行狀態,不處於的話則將command從阻塞佇列移除,並且拒絕任務 4、如果執行緒池裡沒有了執行緒,則建立新的執行緒去執行獲取阻塞佇列的任務執行 5、如果以上都沒執行成功,則需要開啟最大執行緒池裡的執行緒來執行任務,失敗的話就丟棄
有時候再多的文字也不如一個流程圖來的明白,所以還是畫了個execute的流程圖給大家方便理解。
2.addWorker(Runnable firstTask, boolean core)
private boolean addWorker(Runnable firstTask, boolean core) {
//外部迴圈標記
retry:
//外層死迴圈
for (;;) {
//獲取執行緒池控制狀態
int c = ctl.get();
//獲取runState
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/**
*1.如果執行緒池runState至少已經是SHUTDOWN
*2\. 有一個是false則addWorker失敗,看false的情況
* - runState==SHUTDOWN,即狀態已經大於SHUTDOWN了
* - firstTask為null,即傳進來的任務為空,結合上面就是runState是SHUTDOWN,但是
* firstTask不為空,代表執行緒池已經關閉了還在傳任務進來
* - 佇列為空,既然任務已經為空,佇列為空,就不需要往執行緒池新增任務了
*/
if (rs >= SHUTDOWN && //runState大於等於SHUTDOWN,初始位RUNNING
! (rs == SHUTDOWN && //runState等於SHUTDOWN
firstTask == null && //firstTask為null
! workQueue.isEmpty())) //workQueue佇列不為空
return false;
//內層死迴圈
for (;;) {
//獲取執行緒池的workerCount數量
int wc = workerCountOf(c);
//如果workerCount超出最大值或者大於corePoolSize/maximumPoolSize
//返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//通過CAS操作,使workerCount數量+1,成功則跳出迴圈,回到retry標記
if (compareAndIncrementWorkerCount(c))
break retry;
//CAS操作失敗,再次獲取執行緒池的控制狀態
c = ctl.get(); // Re-read ctl
//如果當前runState不等於剛開始獲取的runState,則跳出內層迴圈,繼續外層迴圈
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
//CAS由於更改workerCount而失敗,繼續內層迴圈
}
}
//通過以上迴圈,能執行到這是workerCount成功+1了
//worker開始標記
boolean workerStarted = false;
//worker新增標記
boolean workerAdded = false;
//初始化worker為null
Worker w = null;
try {
//初始化一個當前Runnable物件的worker物件
w = new Worker(firstTask);
//獲取該worker對應的執行緒
final Thread t = w.thread;
//如果執行緒不為null
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.
//獲取鎖後再次檢查,獲取執行緒池runState
int rs = runStateOf(ctl.get());
//當runState小於SHUTDOWN或者runState等於SHUTDOWN並且firstTask為null
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//執行緒已存活
if (t.isAlive()) // precheck that t is startable
//執行緒未啟動就存活,丟擲IllegalThreadStateException異常
throw new IllegalThreadStateException();
//將worker物件新增到workers集合當中
workers.add(w);
//獲取workers集合的大小
int s = workers.size();
//如果大小超過largestPoolSize
if (s > largestPoolSize)
//重新設定largestPoolSize
largestPoolSize = s;
//標記worker已經被新增
workerAdded = true;
}
} finally {
//釋放鎖
mainLock.unlock();
}
//如果worker新增成功
if (workerAdded) {
//啟動執行緒
t.start();
//標記worker已經啟動
workerStarted = true;
}
}
} finally {
//如果worker沒有啟動成功
if (! workerStarted)
//workerCount-1的操作
addWorkerFailed(w);
}
//返回worker是否啟動的標記
return workerStarted;
}
複製程式碼
我們也簡單說一下這個程式碼的流程吧,還真的是挺難的,博主寫的時候都停了好多次,想砸鍵盤的說:
1、獲取執行緒池的控制狀態,進行判斷,不符合則返回false,符合則下一步 2、死迴圈,判斷workerCount是否大於上限,或者大於corePoolSize/maximumPoolSize,沒有的話則對workerCount+1操作, 3、如果不符合上述判斷或+1操作失敗,再次獲取執行緒池的控制狀態,獲取runState與剛開始獲取的runState相比,不一致則跳出內層迴圈繼續外層迴圈,否則繼續內層迴圈 4、+1操作成功後,使用重入鎖ReentrantLock來保證往workers當中新增worker例項,新增成功就啟動該例項。
接下來看看流程圖來理解一下上面程式碼的一個執行流程
3.addWorkerFailed(Worker w)
addWorker方法新增worker失敗,並且沒有成功啟動任務的時候,就會呼叫此方法,將任務從workers中移除,並且workerCount做-1操作。
private void addWorkerFailed(Worker w) {
//重入鎖
final ReentrantLock mainLock = this.mainLock;
//獲取鎖
mainLock.lock();
try {
//如果worker不為null
if (w != null)
//workers移除worker
workers.remove(w);
//通過CAS操作,workerCount-1
decrementWorkerCount();
tryTerminate();
} finally {
//釋放鎖
mainLock.unlock();
}
}
複製程式碼
4.tryTerminate()
當對執行緒池執行了非正常成功邏輯的操作時,都會需要執行tryTerminate嘗試終止執行緒池
final void tryTerminate() {
//死迴圈
for (;;) {
//獲取執行緒池控制狀態
int c = ctl.get();
/*
*執行緒池處於RUNNING狀態
*執行緒池狀態最小大於TIDYING
*執行緒池==SHUTDOWN並且workQUeue不為空
*直接return,不能終止
*/
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//如果workerCount不為0
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
//獲取執行緒池的鎖
final ReentrantLock mainLock = this.mainLock;
//獲取鎖
mainLock.lock();
try {
//通過CAS操作,設定執行緒池狀態為TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
//設定執行緒池的狀態為TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
//傳送釋放訊號給在termination條件上等待的執行緒
termination.signalAll();
}
return;
}
} finally {
//釋放鎖
mainLock.unlock();
}
// else retry on failed CAS
}
}
複製程式碼
5.runWorker(Worker w)
該方法的作用就是去執行任務
final void runWorker(Worker w) {
//獲取當前執行緒
Thread wt = Thread.currentThread();
//獲取worker裡的任務
Runnable task = w.firstTask;
//將worker例項的任務賦值為null
w.firstTask = null;
/*
*unlock方法會呼叫AQS的release方法
*release方法會呼叫具體實現類也就是Worker的tryRelease方法
*也就是將AQS狀態置為0,允許中斷
*/
w.unlock(); // allow interrupts
//是否突然完成
boolean completedAbruptly = true;
try {
//worker例項的task不為空,或者通過getTask獲取的不為空
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
/*
*獲取執行緒池的控制狀態,至少要大於STOP狀態
*如果狀態不對,檢查當前執行緒是否中斷並清除中斷狀態,並且再次檢查執行緒池狀態是否大於STOP
*如果上述滿足,檢查該物件是否處於中斷狀態,不清除中斷標記
*/
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
task = null;
//已完成任務數+1
w.completedTasks++;
//釋放鎖
w.unlock();
}
}
completedAbruptly = false;
} finally {
//處理並退出當前worker
processWorkerExit(w, completedAbruptly);
}
}
複製程式碼
接下來我們用文字來說明一下執行任務這個方法的具體邏輯和流程。
- 首先在方法一進來,就執行了w.unlock(),這是為了將AQS的狀態改為0,因為只有getState() >= 0的時候,執行緒才可以被中斷;
- 判斷firstTask是否為空,為空則通過getTask()獲取任務,不為空接著往下執行
- 判斷是否符合中斷狀態,符合的話設定中斷標記
- 執行beforeExecute(),task.run(),afterExecute()方法
- 任何一個出異常都會導致任務執行的終止;進入processWorkerExit來退出任務
- 正常執行的話會接著回到步驟2
附上一副簡單的流程圖:
6.getTask()
在上面的runWorker方法當中我們可以看出,當firstTask為空的時候,會通過該方法來接著獲取任務去執行,那我們就看看獲取任務這個方法到底是怎麼樣的?
private Runnable getTask() {
//標誌是否獲取任務超時
boolean timedOut = false; // Did the last poll() time out?
//死迴圈
for (;;) {
//獲取執行緒池的控制狀態
int c = ctl.get();
//獲取執行緒池的runState
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/*
*判斷執行緒池的狀態,出現以下兩種情況
*1、runState大於等於SHUTDOWN狀態
*2、runState大於等於STOP或者阻塞佇列為空
*將會通過CAS操作,進行workerCount-1並返回null
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//獲取執行緒池的workerCount
int wc = workerCountOf(c);
// Are workers subject to culling?
/*
*allowCoreThreadTimeOut:是否允許core Thread超時,預設false
*workerCount是否大於核心核心執行緒池
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
*1、wc大於maximumPoolSize或者已超時
*2、佇列不為空時保證至少有一個任務
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
/*
*通過CAS操作,workerCount-1
*能進行-1操作,證明wc大於maximumPoolSize或者已經超時
*/
if (compareAndDecrementWorkerCount(c))
//-1操作成功,返回null
return null;
//-1操作失敗,繼續迴圈
continue;
}
try {
/*
*wc大於核心執行緒池
*執行poll方法
*小於核心執行緒池
*執行take方法
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//判斷任務不為空返回任務
if (r != null)
return r;
//獲取一段時間沒有獲取到,獲取超時
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
複製程式碼
還是文字解說一下上面的程式碼邏輯和流程:
- 獲取執行緒池控制狀態和runState,判斷執行緒池是否已經關閉或者正在關閉,是的話則workerCount-1操作返回null
- 獲取workerCount判斷是否大於核心執行緒池
- 判斷workerCount是否大於最大執行緒池數目或者已經超時,是的話workerCount-1,-1成功則返回null,不成功則回到步驟1重新繼續
- 判斷workerCount是否大於核心執行緒池,大於則用poll方法從佇列獲取任務,否則用take方法從佇列獲取任務
- 判斷任務是否為空,不為空則返回獲取的任務,否則回到步驟1重新繼續
接下來依然有一副流程圖:
7.processWorkerExit
明顯的,在執行任務當中,會去獲取任務進行執行,那既然是執行任務,肯定就會有執行完或者出現異常中斷執行的時候,那這時候肯定也會有相對應的操作,至於具體操作是怎麼樣的,我們還是直接去看原始碼最實際。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
/*
*completedAbruptly:在runWorker出現,代表是否突然完成的意思
*也就是在執行任務過程當中出現異常,就會突然完成,傳true
*
*如果是突然完成,需要通過CAS操作,workerCount-1
*不是突然完成,則不需要-1,因為getTask方法當中已經-1
*
*下面的程式碼註釋貌似與程式碼意思相反了
*/
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
//生成重入鎖
final ReentrantLock mainLock = this.mainLock;
//獲取鎖
mainLock.lock();
try {
//執行緒池統計的完成任務數completedTaskCount加上worker當中完成的任務數
completedTaskCount += w.completedTasks;
//從HashSet<Worker>中移除
workers.remove(w);
} finally {
//釋放鎖
mainLock.unlock();
}
//因為上述操作是釋放任務或執行緒,所以會判斷執行緒池狀態,嘗試終止執行緒池
tryTerminate();
//獲取執行緒池的控制狀態
int c = ctl.get();
//判斷runState是否小魚STOP,即是RUNNING或者SHUTDOWN
//如果是RUNNING或者SHUTDOWN,代表沒有成功終止執行緒池
if (runStateLessThan(c, STOP)) {
/*
*是否突然完成
*如若不是,代表已經沒有任務可獲取完成,因為getTask當中是while迴圈
*/
if (!completedAbruptly) {
/*
*allowCoreThreadTimeOut:是否允許core thread超時,預設false
*min-預設是corePoolSize
*/
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//允許core thread超時並且佇列不為空
//min為0,即允許core thread超時,這樣就不需要維護核心核心執行緒池了
//如果workQueue不為空,則至少保持一個執行緒存活
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//如果workerCount大於min,則表示滿足所需,可以直接返回
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//如果是突然完成,新增一個空任務的worker執行緒--這裡我也不太理解
addWorker(null, false);
}
}
複製程式碼
- 首先判斷執行緒是否突然終止,如果是突然終止,通過CAS,workerCount-1
- 統計執行緒池完成任務數,並將worker從workers當中移除
- 判斷執行緒池狀態,嘗試終止執行緒池
- 執行緒池沒有成功終止
- 判斷是否突然完成任務,不是則進行下一步,是則進行第三步
- 如允許核心執行緒超時,佇列不為空,則至少保證一個執行緒存活
- 新增一個空任務的worker執行緒
Worker內部類
我們在上面已經算是挺詳細地講了執行緒池執行任務execute
的執行流程和一些細節,在上面頻繁地出現了一個字眼,那就是worker例項,那麼這個worker究竟是什麼呢?裡面都包含了一些什麼資訊,以及worker這個任務究竟是怎麼執行的呢?
我們就在這個部分來介紹一下吧,還是直接上原始碼:
我們可以看到Worker內部類繼承AQS同步器並且實現了Runnable介面,所以Worker很明顯就是一個可執行任務並且又可以控制中斷、起到鎖效果的類。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** 工作執行緒,如果工廠失敗則為空. */
final Thread thread;
/** 初始化任務,有可能為空 */
Runnable firstTask;
/** 已完成的任務計數 */
volatile long completedTasks;
/**
* 建立並初始化第一個任務,使用執行緒工廠來建立執行緒
* 初始化有3步
*1、設定AQS的同步狀態為-1,表示該物件需要被喚醒
*2、初始化第一個任務
*3、呼叫ThreadFactory來使自身建立一個執行緒,並賦值給worker的成員變數thread
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//重寫Runnable的run方法
/** Delegates main run loop to outer runWorker */
public void run() {
//呼叫ThreadPoolExecutor的runWorker方法
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
//代表是否獨佔鎖,0-非獨佔 1-獨佔
protected boolean isHeldExclusively() {
return getState() != 0;
}
//重寫AQS的tryAcquire方法嘗試獲取鎖
protected boolean tryAcquire(int unused) {
//嘗試將AQS的同步狀態從0改為1
if (compareAndSetState(0, 1)) {
//如果改變成,則將當前獨佔模式的執行緒設定為當前執行緒並返回true
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
//否則返回false
return false;
}
//重寫AQS的tryRelease嘗試釋放鎖
protected boolean tryRelease(int unused) {
//設定當前獨佔模式的執行緒為null
setExclusiveOwnerThread(null);
//設定AQS同步狀態為0
setState(0);
//返回true
return true;
}
//獲取鎖
public void lock() { acquire(1); }
//嘗試獲取鎖
public boolean tryLock() { return tryAcquire(1); }
//釋放鎖
public void unlock() { release(1); }
//是否被獨佔
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
複製程式碼
小結
寫這個執行緒池就真的是不容易了,歷時兩個星期,中途有很多的地方不懂,而且《Java併發程式設計的藝術》的這本書當中對執行緒池的介紹其實並不算多,所以自己看起來也挺痛苦的,還經常會看了這個方法就不知道為什麼要呼叫這個以及呼叫這個方法是出何用意。而且在這學習的過程當中,有在懷疑自己的學習方法對不對,因為也有人跟我說不需要一句句去看去分析原始碼,只需要知道流程就可以了,但是後來還是想想按照自己的學習路線走,多讀原始碼總是有好處的,在這裡我也給程式猿一些建議,有自己的學習方法的時候,按照自己的方式堅定走下去。
參考資料
方騰飛:《Java併發程式設計的藝術》
如需轉載,請務必註明出處,畢竟一塊塊搬磚也不是容易的事情。