Inside AbstractQueuedSynchronizer (3)

3.4 Template Method node

    AbstractQueuedSynchronizer提供瞭如下幾個protected方法用於子類改寫 多線程

Java代碼   收藏代碼
  1. protected boolean tryAcquire(int arg)  
  2. protected boolean tryRelease(int arg)  
  3. protected int tryAcquireShared(int arg)  
  4. protected boolean tryReleaseShared(int arg)  
  5. protected boolean isHeldExclusively()  

    這幾個方法的默認實現是拋出UnsupportedOperationException,子類能夠根據須要進行改寫。 併發

 

    AbstractQueuedSynchronizer中最基本的acquire流程的相關代碼以下: app

Java代碼   收藏代碼
  1. public final void acquire(int arg) {  
  2.     if (!tryAcquire(arg) &&  
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
  4.         selfInterrupt();  
  5. }  
  6.   
  7. final boolean acquireQueued(final Node node, int arg) {  
  8.     boolean failed = true;  
  9.     try {  
  10.         boolean interrupted = false;  
  11.         for (;;) {  
  12.             final Node p = node.predecessor();  
  13.             if (p == head && tryAcquire(arg)) {  
  14.                 setHead(node);  
  15.                 p.next = null// help GC  
  16.                 failed = false;  
  17.                 return interrupted;  
  18.             }  
  19.             if (shouldParkAfterFailedAcquire(p, node) &&  
  20.                 parkAndCheckInterrupt())  
  21.                 interrupted = true;  
  22.         }  
  23.     } finally {  
  24.         if (failed)  
  25.             cancelAcquire(node);  
  26.     }  
  27. }  

    若是tryAcquire失敗,那麼當前線程可能會被enqueue到WaitQueue,而後被阻塞。 shouldParkAfterFailedAcquire方法會確保每一個線程在被阻塞以前,其對應WaitQueue中的節點的waitStatus被設置爲Node.SIGNAL(-1),以便在release時避免沒必要要的unpark操做。此外shouldParkAfterFailedAcquire還會清理WaitQueue中已經超時或者取消的Node。須要注意的是,在某個線程最終被阻塞以前,tryAcquire可能會被屢次調用。 ide

 

    AbstractQueuedSynchronizer中最基本的release流程的相關代碼以下: 函數

Java代碼   收藏代碼
  1. public final boolean release(int arg) {  
  2.     if (tryRelease(arg)) {  
  3.         Node h = head;  
  4.         if (h != null && h.waitStatus != 0)  
  5.             unparkSuccessor(h);  
  6.         return true;  
  7.     }  
  8.     return false;  
  9. }  
  10.   
  11. private void unparkSuccessor(Node node) {  
  12.     /* 
  13.      * If status is negative (i.e., possibly needing signal) try 
  14.      * to clear in anticipation of signalling.  It is OK if this 
  15.      * fails or if status is changed by waiting thread. 
  16.      */  
  17.     int ws = node.waitStatus;  
  18.     if (ws < 0)  
  19.         compareAndSetWaitStatus(node, ws, 0);  
  20.   
  21.     /* 
  22.      * Thread to unpark is held in successor, which is normally 
  23.      * just the next node.  But if cancelled or apparently null, 
  24.      * traverse backwards from tail to find the actual 
  25.      * non-cancelled successor. 
  26.      */  
  27.     Node s = node.next;  
  28.     if (s == null || s.waitStatus > 0) {  
  29.         s = null;  
  30.         for (Node t = tail; t != null && t != node; t = t.prev)  
  31.             if (t.waitStatus <= 0)  
  32.                 s = t;  
  33.     }  
  34.     if (s != null)  
  35.         LockSupport.unpark(s.thread);  
  36. }  

    release方法中,老是總head節點開始向後查找sucessor。只有當該sucessor的waitStatus被設置的狀況下才會調用unparkSuccessor。unparkSuccessor方法中首先清除以前設置的Node.waitStatus,而後向後查找而且喚醒第一個須要被喚醒的sucessor。須要注意的是,if (s == null || s.waitStatus > 0)這個分支中,查找是從tail節點開始,根據prev引用向前進行。在Inside AbstractQueuedSynchronizer (2)   中提到過,Node.next爲null並不必定意味着沒有sucessor,雖然WaitQueue是個雙向鏈表,可是根據next引用向後查找sucessor不靠譜,而根據prev引用向前查找predecessor老是靠譜。 ui

 

3.5 Fairness this

    到目前爲止咱們已經知道,WaitQueue是個FIFO的隊列,喚醒也老是從head開始。可是AbstractQueuedSynchronizer卻並不必定是公平的(實際上,大多數狀況下都是在非公平模式下工做)。若是在看一遍acquire方法會發現,tryAcquire的調用順序先於acquireQueued,也就是說後來的線程可能在等待中的線程以前acquire成功。這種場景被稱爲barging FIFO strategy,它能提供更高的吞吐量。 spa

 

    大多數AbstractQueuedSynchronizer的子類都同時提供了公平和非公平的實現,例如ReentrantLock提供了NonfairSync和FairSync。例如其FairSync的tryAcquire方法以下: .net

Java代碼   收藏代碼
  1. protected final boolean tryAcquire(int acquires) {  
  2.     final Thread current = Thread.currentThread();  
  3.     int c = getState();  
  4.     if (c == 0) {  
  5.         if (!hasQueuedPredecessors() &&  
  6.             compareAndSetState(0, acquires)) {  
  7.             setExclusiveOwnerThread(current);  
  8.             return true;  
  9.         }  
  10.     }  
  11.     else if (current == getExclusiveOwnerThread()) {  
  12.         int nextc = c + acquires;  
  13.         if (nextc < 0)  
  14.             throw new Error("Maximum lock count exceeded");  
  15.         setState(nextc);  
  16.         return true;  
  17.     }  
  18.     return false;  
  19. }  

   tryAcquire方法返回true的條件之一是!hasQueuedPredecessors() 。hasQueuedPredecessors的代碼以下:

Java代碼   收藏代碼
  1. public final boolean hasQueuedPredecessors() {  
  2.     // The correctness of this depends on head being initialized  
  3.     // before tail and on head.next being accurate if the current  
  4.     // thread is first in queue.  
  5.     Node t = tail; // Read fields in reverse initialization order  
  6.     Node h = head;  
  7.     Node s;  
  8.     return h != t &&  
  9.         ((s = h.next) == null || s.thread != Thread.currentThread());  
  10. }  

    綜上, FairSync優先確保等待中線程先acquire成功。可是公平性也不是絕對的:在一個多線程併發的環境下,就算鎖的獲取是公平的,也不保證後續的其它處理過程的前後順序。

 

    既然默認狀況下使用的都是NonfairSync,那麼FairSync適合什麼樣的場景呢?若是被鎖所保護的代碼段的執行時間比較長,而應用又不能接受線程飢餓(NonfairSync可能會致使雖然某個線程長時間排隊,可是仍然沒法得到鎖的狀況)的場景下能夠考慮使用FairSync。對於ReentrantLock,在其構造函數中傳入true,便可構造一把公平鎖。

相關文章
相關標籤/搜索