系列目錄
- 【詳解】ThreadPoolExecutor原始碼閱讀(一)
- 【詳解】ThreadPoolExecutor原始碼閱讀(二)
- 【詳解】ThreadPoolExecutor原始碼閱讀(三)
AQS在Worker中的應用——標識空閒or非空閒工作執行緒
我對這個上鎖一直搞不懂,雖然有註釋說是允許中斷啥的,但是還是一頭霧水,就打算直接看程式碼分析。第一眼看到這個lock的時候,我就嚇到了。
產生了第一個問題:"啥,一上鎖,多個執行緒不是就要同步排隊了嘛? 而且也沒這必要啊! "
看清楚了才知道,鎖來自於方法引數Worker,也就是說,每個執行緒請求的同步鎖都是各自的Worker的鎖,故不存在這些個執行緒競爭一個鎖的情況。
那問題又來了,我自己的鎖,又沒人跟我搶,犯得著每做一個任務都上鎖嗎?
實際上是有的,只是在這個方法裡,不會發生競爭。
注:此段程式碼同【詳解】ThreadPoolExecutor原始碼閱讀(一) 中的runWorker。
final void runWorker(Worker w) { //獲得當前執行這段程式碼的執行緒 Thread wt = Thread.currentThread(); //先嚐試從worker取得初始任務 Runnable task = w.firstTask; w.firstTask = null; //允許中斷,unlock後state=1,中斷方法獲取到鎖,則判斷為空閒執行緒,可中斷 w.unlock(); boolean completedAbruptly = true; try { //不斷地取任務執行、 其中getTask提供阻塞。如果getTask返回null則退出迴圈 while (task != null || (task = getTask()) != null) { //獲取鎖,標識此執行緒正在工作,非空閒執行緒 w.lock(); 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==true completedAbruptly = false; } finally { //工作執行緒退出的處理操作,如獲取當前worker完成的任務量 //如果異常退出,還需彌補,補充工作執行緒等等 processWorkerExit(w, completedAbruptly); } }
interruptIdleWorkers 中斷空閒執行緒
註釋不是說了嗎,允許中斷,那肯定跟中斷有關,朝這個方向去找啊。當然,我當時並沒有這樣去找,而是機緣巧合,看到ThreadPoolExecutor其他程式碼的時候突然意識到的。
我先看到了shutdown方法,發現有中斷空閒Worker的方法。但是在此之前,我並不知道執行緒池是如何區別Worker執行緒是空閒還是忙碌的,只知道執行緒池有workers集合用來儲存建立的Worker。
於是,我就順著方法檢視下去。找到關閉空閒Worker方法的實現。
注:shutdown的語義是,關閉執行緒池,停止接收新的任務,繼續執行任務佇列中的任務。中斷多餘的空閒執行緒。
public void shutdown() { final ReentrantLock mainLock = this.mainLock; //獲取執行緒池鎖 mainLock.lock(); try { //檢查執行執行緒是否有權關閉執行緒池,暫未深入瞭解 checkShutdownAccess(); //更改執行緒池執行狀態為SHUTDOWN advanceRunState(SHUTDOWN); //中斷空閒執行緒 interruptIdleWorkers(); //鉤子函式 onShutdown(); } finally { mainLock.unlock(); } tryTerminate(); }
private void interruptIdleWorkers() { interruptIdleWorkers(false);
}
interruptIdleWorkers方法的註釋:
關閉等待任務的執行緒(也就是沒有被上鎖的執行緒),由此可得Worker有沒有獲得鎖,是區分其是否空閒的標誌。結合原始碼:
private void interruptIdleWorkers(boolean onlyOne) { //獲取執行緒池的鎖,保持獨佔訪問 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //遍歷workers集合中的所有工作執行緒 for (Worker w : workers) { //獲得worker物件中的執行緒引用 Thread t = w.thread; //如果獲得鎖成功,則中斷對應執行緒 //如果工作執行緒正在執行任務,因為開始執行前,任務會獲取worker的鎖,故其無法被中斷 //如果工作執行緒正在等待任務,因其沒獲得鎖,則當前執行緒可以獲得其worker的鎖,此工作執行緒被中斷 if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } //如果只需要關閉一個工作執行緒,則到此為止 if (onlyOne) break; } } finally { mainLock.unlock(); } }
那問題又來了,如何終止已經開始的任務呢?
這裡終止已經開始的任務,就是shutdownNow方法要做的。(shutdownNow就是停止所有任務,已經開始也要停止。其對應的執行緒池狀態是STOP)
前面由於無法獲取到Worker的鎖,故無法通過interruptIdleWorkers方法將其中斷。但是ThreadPoolExecutor還提供了interruptWorkers方法,該方法不用獲取鎖,直接呼叫Worker的interruptIfstarted方法中斷執行緒。
AQS(AbstractQueuedSynchronizer)在Worker中的鎖管理方式
AQS是基於狀態和等待佇列的同步器,這個例項中,Worker繼承於AQS。AQS的acquire依賴於tryAcquire,release依賴於tryAcquire。而這兩個方法它自己都沒有實現,而是由子類提供。(模板方法設計模式的一種體現)。
我們先來看看獲取鎖的操作,以下程式碼來自AQS
public final void acquire(int arg) { //注意,java表示式會短路,如果前面的結果使得表示式結果固定,那麼後面的程式碼就不會被執行 //這裡如果tryAcquire方法返回true, 那麼!tryAcquire就是false,false '與' 任何東西都是false,故後面的表示式不會被執行 //也就是說如果,一次請求獲取成功,則此方法直接結束,返回。如果請求失敗則加入到等待佇列中,程式碼在這裡停頓 //如果等待過程被中斷,則中斷當前執行緒 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
AQS沒有提供tryAcquire方法的實現
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
acquire依賴於tryAcquire方法,如果該方法成功,則acquire方法直接返回,如果失敗,則將當前執行緒加入等待佇列(此操作將park當前執行緒,使其進入waiting狀態)。
我們來看看Worker中是如何實現tryAcquire方法的:
protected boolean tryAcquire(int unused) { //這裡指明瞭引數無用,方法體內都是寫死的 //利用CAS, 如果當前state值為0,則更改為1 //如果其他執行緒已獲得鎖,那麼state就是1, 而不是預期的0,則此方法失敗 if (compareAndSetState(0, 1)) { //設定當前獨佔擁有者執行緒 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; }
我們再來看看release操作,以下程式碼來自AQS:
public final boolean release(int arg) { //嘗試釋放,如果釋放成功,則喚醒等待佇列中的第一個執行緒 if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
同樣的,AQS沒有實現tryRelease方法
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
Worker中的實現:
protected boolean tryRelease(int unused) { //清空當前獨佔擁有者 setExclusiveOwnerThread(null); //設定狀態為0 setState(0); return true; }
將當前獲取獨佔鎖的執行緒置為null,然後將state為0,這裡與前面tryAcquire一一對應。這個時候其他執行緒就可以獲取鎖了。