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
其邏輯並不複雜:對於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操做相關的代碼以下:
addWaiter方法中的那個if分支,實際上是一種優化,若是失敗那麼會調用enq方法進行enqueue。須要注意的是,以上代碼中對next引用的設定是在enqueue成功以後進行的。這樣作雖然沒有併發問題,可是在判斷一個node是否有sucessor時,不能僅僅經過next == null來判斷,由於enqueue和設置next引用這兩個步驟不是一個原子操做。
對WaitQueue進行dequeue操做相關的代碼以下:
setHead方法沒有用到鎖,也沒有使用CAS,這樣沒有併發問題?沒有,由於這個方法只會被持有鎖的線程所調用,此時只須要將head指向持有鎖的線程對應的node便可。