Inside AbstractQueuedSynchronizer (2)

3 AbstractQueuedSynchronizer java

 

3.1 Inheritance node

    AbstractQueuedSynchronizer繼承自AbstractOwnableSynchronizer。AbstractOwnableSynchronizer繼承自Object而且實現了Serializable接口,它只有一個成員變量private transient Thread exclusiveOwnerThread以及對應的getter/setter方法。該成員變量用於保存當前擁有排他訪問權的線程。須要注意的是,該成員變量沒有用volatile關鍵字修飾。 併發


3.2 State ide

    AbstractQueuedSynchronizer用一個int(private volatile int state)來保存同步狀態,以及對應的getter/setter/compareAndSetState方法。Java6新增了一個AbstractQueuedLongSynchronizer,它用一個long來保存同步狀態,貌似目前沒有被java.util.concurrent中的其它synchronizer所使用。對於ReentrantLock,state爲0則意味着鎖沒有被任何線程持有;不然state保存了持有鎖的線程的重入次數。 oop

 

3.3 WaitQueue 優化

    WaitQueue是AbstractQueuedSynchronizer的核心,它用於保存被阻塞的線程。它的實現是"CLH" (Craig, Landin, and Hagersten) lock queue的一個變種。 this

 

    標準的CLH lock queue一般被用來實現spin lock,它經過TheadLocal變量pred引用隊列中的前一個節點(Node自己沒有指向先後節點的引用),如下是標準的CLH lock queue的一個參考實現: spa

Java代碼   收藏代碼
  1. public class ClhSpinLock {  
  2.     private final ThreadLocal<Node> pred;  
  3.     private final ThreadLocal<Node> node;  
  4.     private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());  
  5.   
  6.     public ClhSpinLock() {  
  7.         this.node = new ThreadLocal<Node>() {  
  8.             protected Node initialValue() {  
  9.                 return new Node();  
  10.             }  
  11.         };  
  12.   
  13.         this.pred = new ThreadLocal<Node>() {  
  14.             protected Node initialValue() {  
  15.                 return null;  
  16.             }  
  17.         };  
  18.     }  
  19.   
  20.     public void lock() {  
  21.         final Node node = this.node.get();  
  22.         node.locked = true;  
  23.         Node pred = this.tail.getAndSet(node);  
  24.         this.pred.set(pred);  
  25.         while (pred.locked) {}  
  26.     }  
  27.   
  28.     public void unlock() {  
  29.         final Node node = this.node.get();  
  30.         node.locked = false;  
  31.         this.node.set(this.pred.get());  
  32.     }  
  33.   
  34.     private static class Node {  
  35.         private volatile boolean locked;  
  36.     }  
  37. }  

    其邏輯並不複雜:對於lock操做,只須要經過一個CAS操做便可將當前線程對應的節點加入到隊列中,而且同時得到了predecessor節點的引用,而後就是等待predecessor釋放鎖;對於unlock操做,只須要將當前線程對應節點的locked成員變量設置爲false。unlock方法中的this.node.set(this.pred.get())主要目的是重用predecessor上的Node對象,這是對GC友好的一個優化。若是不考慮這個優化,那麼this.node.set(new Node())也是能夠的。跟那些TAS(test and set) spin lock和TTAS(test test and set) spin lock相比,CLH spin lock主要是解決了cache-coherence traffic的問題:每一個線程在busy loop的時候,並無競爭同一個狀態,而是隻判斷其對應predecessor的鎖定狀態。若是你擔憂false sharing問題,那麼能夠考慮將鎖定狀態padding到cache line的長度。此外,CLH spin lock經過FIFO的隊列保證了鎖競爭的公平性。 .net

 

    AbstractQueuedSynchronizer的靜態內部類Node維護了一個FIFO的等待隊列。跟CLH不一樣的是,Node中包含了指向predecessor和sucessor的引用。predecessor引用的做用是爲了支持鎖等待超時(timeout)和鎖等待回退(cancellation)的功能。sucessor的做用是爲了支持線程阻塞:在Inside AbstractQueuedSynchronizer (1) 中提到過,AbstractQueuedSynchronizer經過LockSupport實現了線程的block/unblock,所以須要經過successor引用找到後續的線程並將其喚醒(CLH spin lock由於是spin,因此不須要顯式地喚醒)。此外,Node中還包括一個volatile int waitStatus成員變量用於控制線程的阻塞/喚醒,以及避免沒必要要的調用LockSupport的park/unpark方法。須要注意的是,雖然AbstractQueuedSynchronizer在絕大多數狀況下是經過LockSupport進行線程的阻塞/喚醒,可是在特定狀況下也會使用spin lock,static final long spinForTimeoutThreshold = 1000L這個靜態變量設定了使用spin lock的一個閾值。 線程

 

    對WaitQueue進行enqueue操做相關的代碼以下:

Java代碼   收藏代碼
  1. private Node addWaiter(Node mode) {  
  2.     Node node = new Node(Thread.currentThread(), mode);  
  3.     // Try the fast path of enq; backup to full enq on failure  
  4.     Node pred = tail;  
  5.     if (pred != null) {  
  6.         node.prev = pred;  
  7.         if (compareAndSetTail(pred, node)) {  
  8.             pred.next = node;  
  9.             return node;  
  10.         }  
  11.     }  
  12.     enq(node);  
  13.     return node;  
  14. }  
  15.   
  16. private Node enq(final Node node) {  
  17.     for (;;) {  
  18.         Node t = tail;  
  19.         if (t == null) { // Must initialize  
  20.             if (compareAndSetHead(new Node()))  
  21.                 tail = head;  
  22.         } else {  
  23.             node.prev = t;  
  24.             if (compareAndSetTail(t, node)) {  
  25.                 t.next = node;  
  26.                 return t;  
  27.             }  
  28.         }  
  29.     }  
  30. }  

    addWaiter方法中的那個if分支,實際上是一種優化,若是失敗那麼會調用enq方法進行enqueue。須要注意的是,以上代碼中對next引用的設定是在enqueue成功以後進行的。這樣作雖然沒有併發問題,可是在判斷一個node是否有sucessor時,不能僅僅經過next == null來判斷,由於enqueue和設置next引用這兩個步驟不是一個原子操做。

 

    對WaitQueue進行dequeue操做相關的代碼以下:

Java代碼   收藏代碼
  1. private void setHead(Node node) {  
  2.     head = node;  
  3.     node.thread = null;  
  4.     node.prev = null;  
  5. }  

    setHead方法沒有用到鎖,也沒有使用CAS,這樣沒有併發問題?沒有,由於這個方法只會被持有鎖的線程所調用,此時只須要將head指向持有鎖的線程對應的node便可。

相關文章
相關標籤/搜索