Condition
在上一章中,我們大概瞭解了Condition的使用,下面我們來看看Condition再juc的實現。juc下Condition本質上是一個介面,它只定義了這個介面的使用方式,具體的實現其實是交由子類完成。
public interface Condition { void await() throws InterruptedException; void awaitUninterruptibly(); long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); void signalAll(); }
相信這裡不需要筆者對await()、signal()、signalAll()這三個方法做介紹了,甚至有的人看到其他方法也知道這些方法的作用。這裡筆者就簡單介紹下:
- awaitUninterruptibly():釋放鎖後並掛起當前執行緒,此方法會忽略中斷,直到收到條件成立的訊號被喚醒,執行條件成立的程式碼。
- awaitNanos(long nanosTimeout):傳入一個時間,單位為:納秒。如果返回的值>0,則代表等待執行緒在既定時間內獲得條件成立的訊號,可以執行條件成立之後的程式碼;如果小於等於0,則表示執行緒在既定時間內沒有收到條件成立的訊號,不能執行條件成立的程式碼。
- await(long time, TimeUnit unit)和awaitUntil(Date deadline)類似,也是限定一個超時時間,在超時時間內如果收到條件成立的訊號,則返回true執行條件成立之後的程式碼,否則不能執行條件成立後的程式碼。
下面,我們來看看Condition介面在juc包下的具體實現,在juc包下,其實有兩個內部類分別實現了Condition介面,第一個還是我們的老朋友:AbstractQueuedSynchronizer.ConditionObject,第二個是AbstractQueuedLongSynchronizer.ConditionObject,這兩個內部類的實現十分類似,甚至可以時候是一模一樣,這裡筆者介紹AbstractQueuedSynchronizer.ConditionObject的實現。
我們先來看await()方法,看看在這個方法中是如何釋放鎖在阻塞執行緒的,以及當執行緒被喚醒後又是如何重新搶鎖。當執行緒進入await()後,會先在<1>處建立一個封裝了執行緒本身Thread物件的Node節點,然後在<2>處釋放執行緒佔有的鎖。如果有多個執行緒都進入同一條件物件的await()方法,每個執行緒都會有一個對應的Node節點,並且這些Node節點會形成一個佇列。執行緒在釋放鎖後會進入<3>處陷入阻塞,直到收到條件成立的訊號或者執行緒中斷的訊號,於是執行緒從<3>處被喚醒並跳出迴圈。在<4>處我們又看到我們的老朋友acquireQueued(final Node node, long arg),如果記性好的朋友會知道這個方法也可以阻塞執行緒,直到執行緒搶到了鎖。如此,await方法便完成了類似wait()方法的工作。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... public class ConditionObject implements Condition, java.io.Serializable { public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter();//<1> long savedState = fullyRelease(node);//<2> int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this);//<3> if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//<4> interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } } //... }
當然,上面的await()方法遠不止筆者所說的那麼簡單,剛才筆者所說的知識一個大概的脈絡,其中還有很多細節值得深究。下面就請大家跟筆者一起逐行逐句地瞭解await()的原理。
在await()開始,我們可以看到在ConditionObject類本身,會維護兩個欄位firstWaiter和lastWaiter,通過這兩個欄位可以針對某一條件物件形成一個等待佇列。當有執行緒呼叫了addConditionWaiter()方法時,會先在<1>處當前執行緒是否是獨佔鎖的執行緒,如果不是則拋異常,是的話則繼續執行,之後會獲取條件物件的尾節點,如果尾節點不為空,且尾節點的狀態不為Node.CONDITION,這裡會判斷該條件節點已經不是有效的條件節點,會呼叫<2>處的unlinkCancelledWaiters()將條件節點從佇列中移除,然後再重新獲取尾節點。之後會為呼叫執行緒建立一個Node節點,等待狀態為CONDITION(-2),表明這是一個條件節點,如果尾節點為空,代表當前佇列中沒有節點,於是執行<3>處的程式碼把當前節點賦值給頭節點(firstWaiter),如果尾節點不為空,則將當前節點賦值給原先尾節點的nextWaiter,之後將當前節點賦值給尾節點。由於這段程式碼是隻有佔有鎖的執行緒才可以執行的,所以這段程式碼是執行緒安全的。
下面,我們再來看看unlinkCancelledWaiters()是如何將一個狀態非等待的節點從佇列中移除,在條件佇列中,只要狀態不等於CONDITION,就是非條件節點。所有進入條件佇列的節點最開始都是條件節點,但可能因為各種原因從條件節點變成非條件節點,所以要從佇列中移除,至於是哪些原因筆者會在後面細說,這裡只要知道存在條件節點會變成非條件節點的情況,一旦出現這種情況就要把非條件節點從條件佇列移除就行了。這和原先AQS的節點有些類似,也有些許差別,原先的AQS認為只要節點的等待狀態>0,就是一個失效節點,就要從佇列中移除。
當要從條件佇列中移除非條件節點,會從頭節點開始迴圈遍歷,next指向當前節點的下一個節點,用於下一輪判斷是否有條件節點。如果遍歷到的節點是條件節點,則會執行<5>處的程式碼將當前節點賦值給trail,所以trail永遠指向遍歷過程中最靠後的條件節點。如果當前節點不是條件節點,則會清空當前節點的nextWaiter便於GC,同時檢查最靠近當前節點的有效節點trail是否為null,為null代表從頭結點到當前節點都不是有效節點,於是把下一個節點next賦值給頭節點,否則將最靠後的條件節點trail.nextWaiter指向下一個節點,一直到next為null,跳出迴圈。如果從頭節點到尾節點都不是條件節點,trail則永遠為null,而執行<4>處的分支到最後,firstWaiter和lastWaiter會都為null,條件佇列中沒有任何節點。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... private transient volatile Node head; private transient volatile Node tail; //... static final class Node { static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; static final int CANCELLED = 1;//節點狀態為取消 static final int SIGNAL = -1;//當前節點的後繼節點等待喚醒 static final int CONDITION = -2;//節點等待條件成立 volatile int waitStatus;//等待狀態 volatile Thread thread;//節點指向執行緒物件 /** * 一般用於連線到下一個條件節點,或者用特殊值Node.SHARED * 表明這是一個共享節點。 */ Node nextWaiter; //... Node(int waitStatus) { WAITSTATUS.set(this, waitStatus); THREAD.set(this, Thread.currentThread()); } //... } public class ConditionObject implements Condition, java.io.Serializable { //... private transient Node firstWaiter; private transient Node lastWaiter; //... private Node addConditionWaiter() { if (!isHeldExclusively())//<1> throw new IllegalMonitorStateException(); Node t = lastWaiter; // If lastWaiter is cancelled, clean out. if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters();//<2> t = lastWaiter; } Node node = new Node(Node.CONDITION); if (t == null) firstWaiter = node;//<3> else t.nextWaiter = node; lastWaiter = node; return node; } //... private void unlinkCancelledWaiters() { Node t = firstWaiter; Node trail = null; while (t != null) { Node next = t.nextWaiter; if (t.waitStatus != Node.CONDITION) {//<4> t.nextWaiter = null; if (trail == null) firstWaiter = next; else trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t;//<5> t = next; } } } //... }
在把執行緒封裝為Node節點並加入條件佇列後,await()還會呼叫fullyRelease(Node node)釋放鎖,可以看到這裡先呼叫getState(),獲取鎖的引用計數,然後再呼叫release(int arg)清空鎖的引用計數,如果不是佔用鎖的執行緒呼叫此方法,則release(int arg)會返回false,這裡會丟擲IllegalMonitorStateException異常,如果釋放鎖成功,則返回原先的引用計數savedState。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... final int fullyRelease(Node node) { try { int savedState = getState(); if (release(savedState)) return savedState; throw new IllegalMonitorStateException(); } catch (Throwable t) { node.waitStatus = Node.CANCELLED; throw t; } } //... public class ConditionObject implements Condition, java.io.Serializable { //... public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } //... } //... }
之後會判斷當前節點是否在同步佇列,即節點是否在AQS的header、tail的佇列中,這裡肯定是不在的,因為在下面程式碼的<1>處就可以判定當前節點的等待狀態是CONDITION,且節點沒有前驅節點,所以isOnSyncQueue(Node node)將返回false,!isOnSyncQueue(node)為true,這裡會進入<2>處的迴圈將當前執行緒陷入阻塞。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... final boolean isOnSyncQueue(Node node) { if (node.waitStatus == Node.CONDITION || node.prev == null)//<1> return false; if (node.next != null) // If has successor, it must be on queue return true; /* * node.prev can be non-null, but not yet on queue because * the CAS to place it on queue can fail. So we have to * traverse from tail to make sure it actually made it. It * will always be near the tail in calls to this method, and * unless the CAS failed (which is unlikely), it will be * there, so we hardly ever traverse much. */ return findNodeFromTail(node); } //... public class ConditionObject implements Condition, java.io.Serializable { //... public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) {//<2> LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } //... } //... }
既然當前執行緒已經阻塞,那我們就要想辦法喚醒執行緒,讓執行緒繼續往下執行,我們都知道有兩個辦法可以喚醒阻塞在await()的執行緒,一種是呼叫signal()方法,一種是中斷執行緒。我們先從signal()開始講起。
當我們呼叫signal()時,依舊先判斷呼叫執行緒現在是否佔有了鎖,如果不是佔有鎖的執行緒呼叫此方法則丟擲IllegalMonitorStateException異常,如果執行緒為鎖的獨佔執行緒,才接著往下執行。之後獲取條件佇列的頭節點(firstWaiter),頭節點非空的情況下將頭節點傳入到doSignal(Node first)方法中。
觀察doSignal(Node first)這個方法,我們知道第一次傳入的first一定會執行一次transferForSignal(Node node),這個方法的返回結果決定doSignal(Node first)方法體內的迴圈能否繼續。我們先來看看transferForSignal(Node node)這個方法。
如果我們傳入transferForSignal(Node node)的節點是一個條件節點,即等待狀態為CONDITION,那麼<1>處的結果一定為true,此時原先的條件節點變為非條件節點了,換句話說,這個節點應該從條件佇列中移除。之後執行<2>處的程式碼將節點進入到同步佇列,即AQS的佇列,如果入隊成功,會在<3>處返回當前節點的前驅節點。之後判斷節點的前驅節點狀態是否失效,或者前驅節點的等待狀態能否用CAS的方式改為SIGNAL,如果失效或者CAS修改失敗,則喚醒節點的執行緒,最後返回true。
如果是按照這樣的流程執行,那麼transferForSignal(first)的返回未true,(!transferForSignal(first) && (first = firstWaiter) != null)將無法成立,退出迴圈。此外我們注意到doSignal(Node first)方法中在每一次執行迴圈時都會把原先頭節點(first)的下一個節點(first.nextWaiter)賦值給等待物件的頭節點(firstWaiter),不管是transferForSignal(Node node)的返回是true或者false,都會移除原先的頭節點,如果我們傳入條件佇列的節點已經不是條件節點,且佇列中還有別的節點,(!transferForSignal(first) && (first = firstWaiter) != null)就會成立,直到遇到佇列中最靠前的條件節點,或者佇列中已無節點。在迴圈的時候,如果發現頭節點(firstWaiter)為空,表示佇列中沒有多餘的節點,也會置空尾節點(firstWaiter),同時每次迴圈時都會置空原先頭節點(first)的nextWaiter的引用,方便垃圾回收。
所以總結一下doSignal(Node first)的作用,就是從條件佇列的頭節點開始,判斷節點是否是條件節點,如果是的話則修改其等待狀態,並將其放入AQS的同步佇列中,並從條件佇列移除該節點。如果頭節點不是條件節點,則會一邊遍歷一邊移除非條件的節點,直到遇到一個條件節點,或者條件佇列中沒有節點。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... final boolean transferForSignal(Node node) { /* * If cannot change waitStatus, the node has been cancelled. */ //如果傳入的節點的等待狀態不是CONDITION,代表節點已經不是條件節點,返回false if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))//<1> return false; /* * Splice onto queue and try to set waitStatus of predecessor to * indicate that thread is (probably) waiting. If cancelled or * attempt to set waitStatus fails, wake up to resync (in which * case the waitStatus can be transiently and harmlessly wrong). */ //如果傳入的節點是條件節點,到這裡節點的狀態會由CONDITION改為0,將原先的條件節點轉化為非條件節點,並進入AQS的同步佇列 Node p = enq(node);//<2> int ws = p.waitStatus; if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))//<4> LockSupport.unpark(node.thread); return true; } //... private Node enq(Node node) { for (;;) { Node oldTail = tail; if (oldTail != null) { node.setPrevRelaxed(oldTail); if (compareAndSetTail(oldTail, node)) { oldTail.next = node; return oldTail;//<3> } } else { initializeSyncQueue(); } } } //... public class ConditionObject implements Condition, java.io.Serializable { //... public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); } //... private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } //... } //... }
呼叫signal()方法將條件節點從條件佇列移除放入AQS同步佇列後,當先執行緒釋放鎖,喚醒同步佇列頭節點下一個節點的執行緒,於是原先呼叫await()陷入阻塞的執行緒會從迴圈<1>處的LockSupport.park(this)喚醒,接著執行checkInterruptWhileWaiting(node)方法,如果結果不為0則跳出迴圈,如果結果為0,則判斷isOnSyncQueue(node)條件是否成立,如果isOnSyncQueue(node)範圍結果為true則跳出迴圈,否則繼續迴圈。
那麼我們先來看看阻塞執行緒被喚醒後第一個執行的方法checkInterruptWhileWaiting(node),這個方法也簡單,僅僅是判斷執行緒是否是被中斷的,如果不是則返回0,我們的執行緒不是中斷喚醒的,這裡的返回結果一定是0,所以不能進入<2>處的分支跳出迴圈。之後我們判斷isOnSyncQueue(node)的條件是否成立,首先節點的等待狀態不是CONDITION,其次當前節點進入同步佇列後,一定會有前驅節點,所以<3>處的分支一定進不去。如果節點有後繼節點,那麼可以判定這個節點一定是同步佇列裡的節點,但我們的節點進入同步佇列後,我們假定沒有其他執行緒請求鎖,因此我們的節點沒有後繼節點,這裡進不去分支<4>。之後,我們就只能呼叫findNodeFromTail(node)從尾節點開始尋找當前節點,可以確定的是從同步佇列的尾部開始遍歷一定能找到當前節點,因為當前節點就是尾節點。所以isOnSyncQueue(node)最終會返回true,表示當前節點已經在同步佇列,因此while (!isOnSyncQueue(node))判定為false,跳出迴圈。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... public class ConditionObject implements Condition, java.io.Serializable { public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) {//<1> LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//<2> break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } //... private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } } //... final boolean isOnSyncQueue(Node node) { if (node.waitStatus == Node.CONDITION || node.prev == null)//<3> return false; if (node.next != null) // If has successor, it must be on queue <4> return true; /* * node.prev can be non-null, but not yet on queue because * the CAS to place it on queue can fail. So we have to * traverse from tail to make sure it actually made it. It * will always be near the tail in calls to this method, and * unless the CAS failed (which is unlikely), it will be * there, so we hardly ever traverse much. */ return findNodeFromTail(node); } //... private boolean findNodeFromTail(Node node) { // We check for node first, since it's likely to be at or near tail. // tail is known to be non-null, so we could re-order to "save" // one null check, but we leave it this way to help the VM. for (Node p = tail;;) {//<5> if (p == node) return true; if (p == null) return false; p = p.prev; } } //... }
在瞭解瞭如何通過signal()喚醒阻塞執行緒後,我們再來看看如果是中斷阻塞執行緒的流程是怎麼走的。阻塞執行緒被中斷後,從LockSupport.park(this)被喚醒執行checkInterruptWhileWaiting(node)裡面的邏輯,這裡判斷Thread.interrupted()結果為true,所以checkInterruptWhileWaiting(node)的返回結果還要根據transferAfterCancelledWait(node)決定是返回THROW_IE(-1)或是REINTERRUPT(1)。
當呼叫transferAfterCancelledWait(node)方法,如果能成功在<1>處用CAS的方式修改節點的等待狀態從條件節點(CONDITION)改為0,則把當前節點放入同步佇列,並返回true,(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT)將把THROW_IE作為結果返回,表示中斷期間到節點入隊,節點的狀態都是條件節點。如果在<1>處節點的狀態已經不是CONDITION,那麼表示節點的狀態已經被改了,這裡不能確定是別的執行緒先於中斷之前執行signal()導致節點的等待狀態被改,亦或是當前執行緒先被中斷,之後別的執行緒呼叫signal()導致節點的等待狀態被改,一旦出現這種情況,則不能進入<1>處的分支,會繼續往下走,在<2>處會迴圈判斷當前節點是否進入同步佇列,如果不在同步佇列,則一直迴圈直到別的執行緒將當前節點放入同步佇列,最後返回false,(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT)將把REINTERRUPT作為結果返回。但不管是返回THROW_IE或是REINTERRUPT,我們都可以進入分支<3>跳出迴圈。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... public class ConditionObject implements Condition, java.io.Serializable { //... private static final int REINTERRUPT = 1; private static final int THROW_IE = -1; //... public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//<3> break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } //... private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } } //... final boolean transferAfterCancelledWait(Node node) { if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {//<1> enq(node); return true; } /* * If we lost out to a signal(), then we can't proceed * until it finishes its enq(). Cancelling during an * incomplete transfer is both rare and transient, so just * spin. */ while (!isOnSyncQueue(node))//<2> Thread.yield(); return false; } //... }
我們已經瞭解了阻塞執行緒被喚醒、阻塞執行緒被中斷後是如何離開迴圈的。下面我們來看看用這兩種方式離開迴圈的執行緒又會如何執行之後的程式碼,首先執行緒對應的節點已經從條件佇列進入同步隊裡,所以<1>處會呼叫acquireQueued(node, savedState)嘗試獲取鎖,如果競爭鎖失敗則阻塞當前執行緒,直到被喚醒重新開始新一輪的競爭,直到搶鎖成功。並且acquireQueued(node, savedState)會返回當前執行緒是否被中斷,如果是用signal()喚醒的執行緒,是沒有中斷標記的,搶鎖成功從acquireQueued(node, savedState)退出後,返回結果為false,這裡不會進入<1>處的分支,所以interruptMode為0。然後判斷節點如果nextWaiter不為空,則清理條件佇列,移除佇列中非條件節點。由於interruptMode為0,這裡不會進入<3>處的分支。
如果執行緒是被中斷的,在搶鎖成功退出acquireQueued(node, savedState)方法後,返回結果為true,如果判斷interruptMode不為THROW_IE,代表當前執行緒無法判斷中斷是先signal()或者後signal()執行,於是標記中斷模式interruptMode為REINTERRUPT,進入<3>處的分支執行reportInterruptAfterWait(int interruptMode)方法,設定當前執行緒的中斷標記為true。如果interruptMode為THROW_IE,表示在中斷期間到用CAS修改節點的等待狀態為非條件成功,都沒有別的執行緒修改節點狀態,所以進入<3>處的分支執行reportInterruptAfterWait(int interruptMode)會丟擲中斷異常。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //... public class ConditionObject implements Condition, java.io.Serializable { //... private static final int REINTERRUPT = 1; private static final int THROW_IE = -1; //... public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//<1> interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled <2> unlinkCancelledWaiters(); if (interruptMode != 0)//<3> reportInterruptAfterWait(interruptMode); } //... } //... private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); else if (interruptMode == REINTERRUPT) selfInterrupt(); } //... }
下面,筆者再介紹下awaitNanos(long nanosTimeout)方法,在<1>處根據當前系統時間加上外部傳入的等待時間nanosTimeout,算出過期時間。然後用initialNanos儲存一下初始的等待時間,因為這個等待時間在<3>處的迴圈是有可能減少的,後面還需要初始的剩餘時間。然後我們呼叫addConditionWaiter()方法將當前執行緒作為Node節點放入條件佇列,再呼叫fullyRelease(node)釋放當前執行緒對鎖的佔用,之後進入<3>處的迴圈,在這個迴圈中剩餘等待時間nanosTimeout會不斷減少,如果小於等於0則退出迴圈;如果迴圈的過程中判斷剩餘等待時間大於1000ns(SPIN_FOR_TIMEOUT_THRESHOLD)則選入計時阻塞,如果執行緒在阻塞期間內被中斷,或者到達過期時間後執行緒被喚醒,則會執行之後的checkInterruptWhileWaiting(node),這裡就不再對這個方法做介紹,先前介紹過這個方法,並且也講解過中斷和喚醒是如何離開迴圈的。
離開迴圈後,執行緒呼叫acquireQueued(node, savedState)重新搶鎖,搶鎖成功後根據中斷模式interruptMode判斷是要丟擲異常,還是標記執行緒中斷狀態,亦或是無事發生。
如果執行緒是被喚醒或是重新標記執行緒中斷狀態,則會執行<5>處之後的程式碼,這裡會根據過期時間和當前時間算出剩餘時間remaining,一般這個剩餘時間(remaining)都是小於等待時間(initialNanos),除非發生溢位。如果剩餘時間小於等待時間,則返回剩餘時間,否則出現溢位則返回Long.MIN_VALUE。
public class ConditionObject implements Condition, java.io.Serializable { //... static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L; //... public final long awaitNanos(long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // We don't check for nanosTimeout <= 0L here, to allow // awaitNanos(0) as a way to "yield the lock". final long deadline = System.nanoTime() + nanosTimeout;//<1> long initialNanos = nanosTimeout;//<2> Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) {//<3> if (nanosTimeout <= 0L) { transferAfterCancelledWait(node); break; } if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; nanosTimeout = deadline - System.nanoTime();//<4> } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); long remaining = deadline - System.nanoTime(); // avoid overflow<5> return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE; } //... }
最後,筆者再介紹下signalAll()便結束了ReentrantLock原始碼解析的篇章。當然,這裡並沒有完全介紹完Condition在AQS的實現,還剩:awaitUninterruptibly()、await(long time, TimeUnit unit)、awaitUntil(Date deadline),但這些方法和awaitNanos(long nanosTimeout)都大同小異,筆者相信各位看官一定也能獨立思考這幾個方法。
下面,我們來看看signalAll(),其實這個方法只要有前面的基礎知識,也能輕鬆理解,signalAll()和signal()的不同是,signal()呼叫transferForSignal(first)方法只要條件佇列中有一個條件節點轉化為非條件節點並進入同步佇列,則會退出。而signalAll()會呼叫transferForSignal(first)方法把佇列中所有節點傳入並加入到同步佇列,直到條件佇列為空。
public class ConditionObject implements Condition, java.io.Serializable { //... public final void signalAll() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignalAll(first); } //... private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; do { Node next = first.nextWaiter; first.nextWaiter = null; transferForSignal(first); first = next; } while (first != null); } //... }
至此,我們算是瞭解了ReentrantLock的使用方式以及實現原理。不知道大家有沒有這樣的感受,本章與其說是在講ReentrantLock,更像是在講AQS,ReentrantLock的內部類只是實現了AQS要求的一些介面,有自己的特性而已。
事實上,筆者真正的目的也是帶大家去了解AQS,在筆者看來AQS絕對可以說是juc包下的核心且沒有之一,只是AQS太過複雜抽象,需要用多個維度去看待。所以筆者這裡用ReentrantLock作為切入點,和大家一起學習ReentrantLock和AQS。
ReentrantLock到此就告一段落,在後面的章節,筆者還會和大家一起從更多的角度去看待AQS,我們下一篇文章見。