共享模式acquire實現流程
上文我們講解了AbstractQueuedSynchronizer獨佔模式的acquire實現流程,本文趁熱打鐵繼續看一下AbstractQueuedSynchronizer共享模式acquire的實現流程。連續兩篇文章的學習,也可以對比獨佔模式acquire和共享模式acquire的區別,加深對於AbstractQueuedSynchronizer的理解。
先看一下共享模式acquire的實現,方法為acquireShared和acquireSharedInterruptibly,兩者差別不大,區別就在於後者有中斷處理,以acquireShared為例:
1 public final void acquireShared(int arg) { 2 if (tryAcquireShared(arg) < 0) 3 doAcquireShared(arg); 4 }
這裡就能看出第一個差別來了:獨佔模式acquire的時候子類重寫的方法tryAcquire返回的是boolean,即是否tryAcquire成功;共享模式acquire的時候,返回的是一個int型變數,判斷是否<0。doAcquireShared方法的實現為:
1 private void doAcquireShared(int arg) { 2 final Node node = addWaiter(Node.SHARED); 3 boolean failed = true; 4 try { 5 boolean interrupted = false; 6 for (;;) { 7 final Node p = node.predecessor(); 8 if (p == head) { 9 int r = tryAcquireShared(arg); 10 if (r >= 0) { 11 setHeadAndPropagate(node, r); 12 p.next = null; // help GC 13 if (interrupted) 14 selfInterrupt(); 15 failed = false; 16 return; 17 } 18 } 19 if (shouldParkAfterFailedAcquire(p, node) && 20 parkAndCheckInterrupt()) 21 interrupted = true; 22 } 23 } finally { 24 if (failed) 25 cancelAcquire(node); 26 } 27 }
我們來分析一下這段程式碼做了什麼:
- addWaiter,把所有tryAcquireShared<0的執行緒例項化出一個Node,構建為一個FIFO佇列,這和獨佔鎖是一樣的
- 拿當前節點的前驅節點,只有前驅節點是head的節點才能tryAcquireShared,這和獨佔鎖也是一樣的
- 前驅節點不是head的,執行"shouldParkAfterFailedAcquire() && parkAndCheckInterrupt()",for(;;)迴圈,"shouldParkAfterFailedAcquire()"方法執行2次,當前執行緒阻塞,這和獨佔鎖也是一樣的
確實,共享模式下的acquire和獨佔模式下的acquire大部分邏輯差不多,最大的差別在於tryAcquireShared成功之後,獨佔模式的acquire是直接將當前節點設定為head節點即可,共享模式會執行setHeadAndPropagate方法,顧名思義,即在設定head之後多執行了一步propagate操作。setHeadAndPropagate方法原始碼為:
1 private void setHeadAndPropagate(Node node, int propagate) { 2 Node h = head; // Record old head for check below 3 setHead(node); 4 /* 5 * Try to signal next queued node if: 6 * Propagation was indicated by caller, 7 * or was recorded (as h.waitStatus) by a previous operation 8 * (note: this uses sign-check of waitStatus because 9 * PROPAGATE status may transition to SIGNAL.) 10 * and 11 * The next node is waiting in shared mode, 12 * or we don't know, because it appears null 13 * 14 * The conservatism in both of these checks may cause 15 * unnecessary wake-ups, but only when there are multiple 16 * racing acquires/releases, so most need signals now or soon 17 * anyway. 18 */ 19 if (propagate > 0 || h == null || h.waitStatus < 0) { 20 Node s = node.next; 21 if (s == null || s.isShared()) 22 doReleaseShared(); 23 } 24 }
第3行的程式碼設定重設head,第2行的程式碼由於第3行的程式碼要重設head,因此先定義一個Node型變數h獲得原head的地址,這兩行程式碼很簡單。
第19行~第23行的程式碼是獨佔鎖和共享鎖最不一樣的一個地方,我們再看獨佔鎖acquireQueued的程式碼:
1 final boolean acquireQueued(final Node node, int arg) { 2 boolean failed = true; 3 try { 4 boolean interrupted = false; 5 for (;;) { 6 final Node p = node.predecessor(); 7 if (p == head && tryAcquire(arg)) { 8 setHead(node); 9 p.next = null; // help GC 10 failed = false; 11 return interrupted; 12 } 13 if (shouldParkAfterFailedAcquire(p, node) && 14 parkAndCheckInterrupt()) 15 interrupted = true; 16 } 17 } finally { 18 if (failed) 19 cancelAcquire(node); 20 } 21 }
這意味著獨佔鎖某個節點被喚醒之後,它只需要將這個節點設定成head就完事了,而共享鎖不一樣,某個節點被設定為head之後,如果它的後繼節點是SHARED狀態的,那麼將繼續通過doReleaseShared方法嘗試往後喚醒節點,實現了共享狀態的向後傳播。
共享模式release實現流程
上面講了共享模式下acquire是如何實現的,下面再看一下release的實現流程,方法為releaseShared:
1 public final boolean releaseShared(int arg) { 2 if (tryReleaseShared(arg)) { 3 doReleaseShared(); 4 return true; 5 } 6 return false; 7 }
tryReleaseShared方法是子類實現的,如果tryReleaseShared成功,那麼執行doReleaseShared()方法:
1 private void doReleaseShared() { 2 /* 3 * Ensure that a release propagates, even if there are other 4 * in-progress acquires/releases. This proceeds in the usual 5 * way of trying to unparkSuccessor of head if it needs 6 * signal. But if it does not, status is set to PROPAGATE to 7 * ensure that upon release, propagation continues. 8 * Additionally, we must loop in case a new node is added 9 * while we are doing this. Also, unlike other uses of 10 * unparkSuccessor, we need to know if CAS to reset status 11 * fails, if so rechecking. 12 */ 13 for (;;) { 14 Node h = head; 15 if (h != null && h != tail) { 16 int ws = h.waitStatus; 17 if (ws == Node.SIGNAL) { 18 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) 19 continue; // loop to recheck cases 20 unparkSuccessor(h); 21 } 22 else if (ws == 0 && 23 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) 24 continue; // loop on failed CAS 25 } 26 if (h == head) // loop if head changed 27 break; 28 } 29 }
主要是兩層邏輯:
- 頭結點本身的waitStatus是SIGNAL且能通過CAS演算法將頭結點的waitStatus從SIGNAL設定為0,喚醒頭結點的後繼節點
- 頭結點本身的waitStatus是0的話,嘗試將其設定為PROPAGATE狀態的,意味著共享狀態可以向後傳播
Condition的await()方法實現原理----構建等待佇列
我們知道,Condition是用於實現通知/等待機制的,和Object的wait()/notify()一樣,由於本文之前描述AbstractQueuedSynchronizer的共享模式的篇幅不是很長,加之Condition也是AbstractQueuedSynchronizer的一部分,因此將Condition也放在這裡寫了。
Condition分為await()和signal()兩部分,前者用於等待、後者用於喚醒,首先看一下await()是如何實現的。Condition本身是一個介面,其在AbstractQueuedSynchronizer中的實現為ConditionObject:
1 public class ConditionObject implements Condition, java.io.Serializable { 2 private static final long serialVersionUID = 1173984872572414699L; 3 /** First node of condition queue. */ 4 private transient Node firstWaiter; 5 /** Last node of condition queue. */ 6 private transient Node lastWaiter; 7 8 ... 9 }
這裡貼了一些欄位定義,後面都是方法就不貼了,會對重點方法進行分析的。從欄位定義我們可以看到,ConditionObject全域性性地記錄了第一個等待的節點與最後一個等待的節點。
像ReentrantLock每次要使用ConditionObject,直接new一個ConditionObject出來即可。我們關注一下await()方法的實現:
1 public final void await() throws InterruptedException { 2 if (Thread.interrupted()) 3 throw new InterruptedException(); 4 Node node = addConditionWaiter(); 5 int savedState = fullyRelease(node); 6 int interruptMode = 0; 7 while (!isOnSyncQueue(node)) { 8 LockSupport.park(this); 9 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 10 break; 11 } 12 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 13 interruptMode = REINTERRUPT; 14 if (node.nextWaiter != null) // clean up if cancelled 15 unlinkCancelledWaiters(); 16 if (interruptMode != 0) 17 reportInterruptAfterWait(interruptMode); 18 }
第2行~第3行的程式碼用於處理中斷,第4行程式碼比較關鍵,新增Condition的等待者,看一下實現:
1 private Node addConditionWaiter() { 2 Node t = lastWaiter; 3 // If lastWaiter is cancelled, clean out. 4 if (t != null && t.waitStatus != Node.CONDITION) { 5 unlinkCancelledWaiters(); 6 t = lastWaiter; 7 } 8 Node node = new Node(Thread.currentThread(), Node.CONDITION); 9 if (t == null) 10 firstWaiter = node; 11 else 12 t.nextWaiter = node; 13 lastWaiter = node; 14 return node; 15 }
首先拿到佇列(注意資料結構,Condition構建出來的也是一個佇列)中最後一個等待者,緊接著第4行的的判斷,判斷最後一個等待者的waitStatus不是CONDITION的話,執行第5行的程式碼,解綁取消的等待者,因為通過第8行的程式碼,我們看到,new出來的Node的狀態都是CONDITION的。
那麼unlinkCancelledWaiters做了什麼?裡面的流程就不看了,就是一些指標遍歷並判斷狀態的操作,總結一下就是:從頭到尾遍歷每一個Node,遇到Node的waitStatus不是CONDITION的就從佇列中踢掉,該節點的前後節點相連。
接著第8行的程式碼前面說過了,new出來了一個Node,儲存了當前執行緒,waitStatus是CONDITION,接著第9行~第13行的操作很好理解:
- 如果lastWaiter是null,說明FIFO佇列中沒有任何Node,firstWaiter=Node
- 如果lastWaiter不是null,說明FIFO佇列中有Node,原lastWaiter的next指向Node
- 無論如何,新加入的Node程式設計lastWaiter,即新加入的Node一定是在最後面
用一張圖表示一下構建的資料結構就是:
對比學習,我們總結一下Condition構建出來的佇列和AbstractQueuedSynchronizer構建出來的佇列的差別,主要體現在2點上:
- AbstractQueuedSynchronizer構建出來的佇列,頭節點是一個沒有Thread的空節點,其標識作用,而Condition構建出來的佇列,頭節點就是真正等待的節點
- AbstractQueuedSynchronizer構建出來的佇列,節點之間有next與pred相互標識該節點的前一個節點與後一個節點的地址,而Condition構建出來的佇列,只使用了nextWaiter標識下一個等待節點的地址
整個過程中,我們看到沒有使用任何CAS操作,firstWaiter和lastWaiter也沒有用volatile修飾,其實原因很簡單:要await()必然要先lock(),既然lock()了就表示沒有競爭,沒有競爭自然也沒必要使用volatile+CAS的機制去保證什麼。
Condition的await()方法實現原理----執行緒等待
前面我們看了Condition構建等待佇列的過程,接下來我們看一下等待的過程,await()方法的程式碼比較短,再貼一下:
1 public final void await() throws InterruptedException { 2 if (Thread.interrupted()) 3 throw new InterruptedException(); 4 Node node = addConditionWaiter(); 5 int savedState = fullyRelease(node); 6 int interruptMode = 0; 7 while (!isOnSyncQueue(node)) { 8 LockSupport.park(this); 9 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 10 break; 11 } 12 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 13 interruptMode = REINTERRUPT; 14 if (node.nextWaiter != null) // clean up if cancelled 15 unlinkCancelledWaiters(); 16 if (interruptMode != 0) 17 reportInterruptAfterWait(interruptMode); 18 }
構建完畢佇列之後,執行第5行的fullyRelease方法,顧名思義:fullyRelease方法的作用是完全釋放Node的狀態。方法實現為:
1 final int fullyRelease(Node node) { 2 boolean failed = true; 3 try { 4 int savedState = getState(); 5 if (release(savedState)) { 6 failed = false; 7 return savedState; 8 } else { 9 throw new IllegalMonitorStateException(); 10 } 11 } finally { 12 if (failed) 13 node.waitStatus = Node.CANCELLED; 14 } 15 }
這裡第4行獲取state,第5行release的時候將整個state傳過去,理由是某執行緒可能多次呼叫了lock()方法,比如呼叫了10次lock,那麼此執行緒就將state加到了10,所以這裡要將10傳過去,將狀態全部釋放,這樣後面的執行緒才能重新從state=0開始競爭鎖,這也是方法被命名為fullyRelease的原因,因為要完全釋放鎖,釋放鎖之後,如果有競爭鎖的執行緒,那麼就喚醒第一個,這都是release方法的邏輯了,前面的文章詳細講解過。
接著看await()方法的第7行判斷"while(!isOnSyncQueue(node))":
1 final boolean isOnSyncQueue(Node node) { 2 if (node.waitStatus == Node.CONDITION || node.prev == null) 3 return false; 4 if (node.next != null) // If has successor, it must be on queue 5 return true; 6 /* 7 * node.prev can be non-null, but not yet on queue because 8 * the CAS to place it on queue can fail. So we have to 9 * traverse from tail to make sure it actually made it. It 10 * will always be near the tail in calls to this method, and 11 * unless the CAS failed (which is unlikely), it will be 12 * there, so we hardly ever traverse much. 13 */ 14 return findNodeFromTail(node); 15 }
注意這裡的判斷是Node是否在AbstractQueuedSynchronizer構建的佇列中而不是Node是否在Condition構建的佇列中,如果Node不在AbstractQueuedSynchronizer構建的佇列中,那麼呼叫LockSupport的park方法阻塞。
至此呼叫await()方法的執行緒構建Condition等待佇列--釋放鎖--等待的過程已經全部分析完畢。
Condition的signal()實現原理
上面的程式碼分析了構建Condition等待佇列--釋放鎖--等待的過程,接著看一下signal()方法通知是如何實現的:
1 public final void signal() { 2 if (!isHeldExclusively()) 3 throw new IllegalMonitorStateException(); 4 Node first = firstWaiter; 5 if (first != null) 6 doSignal(first); 7 }
首先從第2行的程式碼我們看到,要能signal(),當前執行緒必須持有獨佔鎖,否則丟擲異常IllegalMonitorStateException。
那麼真正操作的時候,獲取第一個waiter,如果有waiter,呼叫doSignal方法:
1 private void doSignal(Node first) { 2 do { 3 if ( (firstWaiter = first.nextWaiter) == null) 4 lastWaiter = null; 5 first.nextWaiter = null; 6 } while (!transferForSignal(first) && 7 (first = firstWaiter) != null); 8 }
第3行~第5行的程式碼很好理解:
- 重新設定firstWaiter,指向第一個waiter的nextWaiter
- 如果第一個waiter的nextWaiter為null,說明當前佇列中只有一個waiter,lastWaiter置空
- 因為firstWaiter是要被signal的,因此它沒什麼用了,nextWaiter置空
接著執行第6行和第7行的程式碼,這裡重點就是第6行的transferForSignal方法:
1 final boolean transferForSignal(Node node) { 2 /* 3 * If cannot change waitStatus, the node has been cancelled. 4 */ 5 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) 6 return false; 7 8 /* 9 * Splice onto queue and try to set waitStatus of predecessor to 10 * indicate that thread is (probably) waiting. If cancelled or 11 * attempt to set waitStatus fails, wake up to resync (in which 12 * case the waitStatus can be transiently and harmlessly wrong). 13 */ 14 Node p = enq(node); 15 int ws = p.waitStatus; 16 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) 17 LockSupport.unpark(node.thread); 18 return true; 19 }
方法本意是將一個節點從Condition佇列轉換為AbstractQueuedSynchronizer佇列,總結一下方法的實現:
- 嘗試將Node的waitStatus從CONDITION置為0,這一步失敗直接返回false
- 當前節點進入呼叫enq方法進入AbstractQueuedSynchronizer佇列
- 當前節點通過CAS機制將waitStatus置為SIGNAL
最後上面的步驟全部成功,返回true,返回true喚醒等待節點成功。從喚醒的程式碼我們可以得出一個重要結論:某個await()的節點被喚醒之後並不意味著它後面的程式碼會立即執行,它會被加入到AbstractQueuedSynchronizer佇列的尾部,只有前面等待的節點獲取鎖全部完畢才能輪到它。
程式碼分析到這裡,我想類似的signalAll方法也沒有必要再分析了,顯然signalAll方法的作用就是將所有Condition佇列中等待的節點逐一佇列中從移除,由CONDITION狀態變為SIGNAL狀態並加入AbstractQueuedSynchronizer佇列的尾部。
程式碼示例
可能大家看了我分析半天程式碼會有點迷糊,這裡最後我貼一段我用於驗證上面Condition結論的示例程式碼,首先建立一個Thread,我將之命名為ConditionThread:
1 /** 2 * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7067904.html 3 */ 4 public class ConditionThread implements Runnable { 5 6 private Lock lock; 7 8 private Condition condition; 9 10 public ConditionThread(Lock lock, Condition condition) { 11 this.lock = lock; 12 this.condition = condition; 13 } 14 15 @Override 16 public void run() { 17 18 if ("執行緒0".equals(JdkUtil.getThreadName())) { 19 thread0Process(); 20 } else if ("執行緒1".equals(JdkUtil.getThreadName())) { 21 thread1Process(); 22 } else if ("執行緒2".equals(JdkUtil.getThreadName())) { 23 thread2Process(); 24 } 25 26 } 27 28 private void thread0Process() { 29 try { 30 lock.lock(); 31 System.out.println("執行緒0休息5秒"); 32 JdkUtil.sleep(5000); 33 condition.signal(); 34 System.out.println("執行緒0喚醒等待執行緒"); 35 } finally { 36 lock.unlock(); 37 } 38 } 39 40 private void thread1Process() { 41 try { 42 lock.lock(); 43 System.out.println("執行緒1阻塞"); 44 condition.await(); 45 System.out.println("執行緒1被喚醒"); 46 } catch (InterruptedException e) { 47 48 } finally { 49 lock.unlock(); 50 } 51 } 52 53 private void thread2Process() { 54 try { 55 System.out.println("執行緒2想要獲取鎖"); 56 lock.lock(); 57 System.out.println("執行緒2獲取鎖成功"); 58 } finally { 59 lock.unlock(); 60 } 61 } 62 63 }
這個類裡面的方法就不解釋了,反正就三個方法片段,根據執行緒名判斷,每個線層執行的是其中的一個程式碼片段。寫一段測試程式碼:
1 /** 2 * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7067904.html 3 */ 4 @Test 5 public void testCondition() throws Exception { 6 Lock lock = new ReentrantLock(); 7 Condition condition = lock.newCondition(); 8 9 // 執行緒0的作用是signal 10 Runnable runnable0 = new ConditionThread(lock, condition); 11 Thread thread0 = new Thread(runnable0); 12 thread0.setName("執行緒0"); 13 // 執行緒1的作用是await 14 Runnable runnable1 = new ConditionThread(lock, condition); 15 Thread thread1 = new Thread(runnable1); 16 thread1.setName("執行緒1"); 17 // 執行緒2的作用是lock 18 Runnable runnable2 = new ConditionThread(lock, condition); 19 Thread thread2 = new Thread(runnable2); 20 thread2.setName("執行緒2"); 21 22 thread1.start(); 23 Thread.sleep(1000); 24 thread0.start(); 25 Thread.sleep(1000); 26 thread2.start(); 27 28 thread1.join(); 29 }
測試程式碼的意思是:
- 執行緒1先啟動,獲取鎖,呼叫await()方法等待
- 執行緒0後啟動,獲取鎖,休眠5秒準備signal()
- 執行緒2最後啟動,獲取鎖,由於執行緒0未使用完畢鎖,因此執行緒2排隊,可以此時由於執行緒0還未signal(),因此執行緒1線上程0執行signal()後,在AbstractQueuedSynchronizer佇列中的順序是線上程2後面的
程式碼執行結果為:
1 執行緒1阻塞 2 執行緒0休息5秒 3 執行緒2想要獲取鎖 4 執行緒0喚醒等待執行緒 5 執行緒2獲取鎖成功 6 執行緒1被喚醒
符合我們的結論:signal()並不意味著被喚醒的執行緒立即執行。由於執行緒2先於執行緒0排隊,因此看到第5行列印的內容,執行緒2先獲取鎖。