上一章我們講了synchronized對象鎖的各種級別。但是,有的時候會被人問到ReentrantLock是怎么實現的么?AQS是啥?公平鎖和非公平鎖怎么實現的么?排他鎖和共享鎖怎么實現么?
就算我們弄懂了前兩章的內容,還是只能不好意思的回答:不會。
相關的類
在文章的開頭,我自己畫了一個相關的類的類圖,讓你可以在開始的時候有個大概的了解。在讀文章的過程中,你也可以時不時回來看一下這個圖,應該會有很大的收獲。
ReentrantLock
首先我們從ReentrantLock的構造函數入手,來看下ReentrantLock是怎么實現的。
ReentrantLock有兩種實現,FairSync公平鎖的實現和NonfairSync非公平鎖的實現。
ReentrantLock的lock和unlock也是調用的對應sync里的lock和unlock。
FairSync和NonfairSync都繼承了Sync類,而Sync則繼承了AbstractQueuedSynchronizer, AbstractQueuedSynchronizer也就是傳說中的AQS,AQS繼承了AbstractOwnableSynchronizer,我們可以把AbstractOwnableSynchronizer叫AOS(這個名字是我自己取的)。
NonfairSync
NonfairSync的代碼特別少,只有兩個方法lock()和tryAcquire()。
我看到這個代碼第一映像是:不是應該lock()跟unlock()方法么,怎么會是lock()和tryAcquire()呢?
實際上,我們看下ReentrantLock的lock()方法和unlock()方法:
也就是說lock調的是NonfairSync的lock()方法,unlock調用的應該是NonfairSync的release()方法。
relase()
不管NonfairSync還是FairSync,release()方法都是在他們的父類AQS里實現的。這意味著,公平鎖和非公平鎖,釋放鎖的步驟是一樣的。
下面是AQS里的release的代碼。
這個就解釋了為什么NonfairSync和FairSync里只有lock了。
lock()
NonfairSync里的lock()我們知道是干什么的了,那么tryAcquire()又是個什么鬼呢?
NonfairSync的實現里tryAcquire()只有一行,return nonfairTryAcquire(acquires)。
我們再來看下nonfairTryAcquire方法。
通過讀nonfairTryAcquire()這個方法,我們知道,實際上這個方法就是試著通過state和對應的thread值去獲取鎖,獲取成功了返回true,失敗了返回false。
如果當前線程已經獲取到鎖了,那么再次調用lock的時候,nextc實際上是+1,然后將nextc設置到state里去。這樣也實現了ReentrantLock的可重入性。
但是,問題來了,tryAcquire這個方法在哪里調用呢?
我們再回過頭來看一下lock方法
compareAndSetState()方法就是對state的一個CAS操作,期望值0,修改值1。成功了,就意味著拿到鎖了。
acquire()方法,是在AQS里實現的。我們看下acquire的代碼。
好了,找到tryAcquire()的調用地方了:實際上在lock里調用的。只不過tryAcquire在AQS里是一個虛方法。真正實現其實還是在子類也就是NonfairSync和FairSync。
FairSync
我們在來看一下FairSync類的實現。
同樣很簡單,跟NonfairSync的區別, 無非就是lock方法少了一次CAS操作,然后tryAcquire方法多了一個hasQueuedPredecessors()的判斷。
這意味著hasQueuedPredecessors()的判斷,很可能是用來保證鎖的公平性的。
鎖的公平實際上就是指先進等待隊列的先拿到鎖。我們從hasQueuedPredecessors的方法名字中其實就可以看出,這個方法就是為了保證當前獲取鎖的線程前面沒有其他等待獲取鎖的線程了,如果有其他線程在前面,當前線程就拿不到鎖。這樣也確實是可以保證得到鎖的順序為先來先得,也就是保證鎖的公平性。
到這里對NonfairSync和FairSync的分析就告一段落了。lock已經轉移到了對AQS里acquire方法的分析,unlock已經轉移到了對AQS里release方法的分析。
AQS
AbstractQueuedSynchronizer,傳說中的AQS。
AQS其實是一種變種的CLH鎖。關于CLH的資料可以看這個blog:
http://blog.csdn.net/aesop_wubo/article/details/7533186。
前面我們說到了lock()已經轉移到了AQS里的acquire()方法。那我們就從acquire()作為入口開始了解AQS。
Acquire獲取鎖的流程如下:
我們在來看下acquire()的代碼:
首先通過tryAcquire()嘗試獲取鎖,如果獲取不到,就創建一個代表當前線程的節點加入到等待隊列的尾部,這個節點的模式為EXCLUSIVE也就是排他模式,對應的還有一個模式是SHARE,也就是共享模式。
tryAcquire()方法我們前面已經講過了,下面主要看一下addWaiter()方法和acquireQueued()方法。
addWaiter
通過addWaiter()的方法名,我們就可以知道這個方法是往等待隊列里加入一個Node。
這個方法應該分為兩部分來讀:
tail != null和tail == null。
tail != null表示當前隊列里有元素,那么就設置當前節點為隊尾節點。
tail== null表示當前隊列里沒有元素,那么就執行enq()方法,將這個節點入隊。
其實我也疑惑,這里直接調enq方法不就行了嗎?為什么還要進行一次判斷呢?希望有大神能給解釋下~
我們再看下enq()方法
enq()方法采用的就是變種CLH算法,先看尾結點是否為空,如果為空就創建一個傀儡結點,頭尾指針都指向這個傀儡結點,這一步只會在隊列初始化時會執行;
如果尾結點非空,就采用CAS操作將當前結點插入到尾結點后面,如果在插入的時候尾結點有變化,就將尾結點向后移動直到移動到最后一個結點為止,然后再把當前結點插入到尾結點后面,尾指針指向當前結點,入隊成功。
acquireQueued
我們再來看一下acquireQueued()方法。
將新加入的結點放入隊列之后,這個結點有兩種狀態,要么獲取鎖,要么就掛起。
如果這個節點的前趨節點是頭節點,就會用tryAcquire()來嘗試獲取鎖。如果成功,就將當前節點設置為頭節點,并且將前趨節點設置為null,這樣方便前趨節點被GC回收。如果失敗就繼續在for循環里獲取,直到獲取成功。
如果這個結點不是頭結點,就看看這個結點是否應該掛起,如果應該掛起,就掛起當前結點,是否應該掛起是通過shouldParkAfterFailedAcquire()方法來判斷的。
看到這里的Node.SIGNAL,可能很多人也疑惑了,這些狀態都代表什么意思呢?
CLH鎖的本質是一個FIFO的隊列,既然是隊列, 那肯定有一個個的節點。
CLH鎖的節點里,只有true和false的狀態。但是AQS的節點有很多。
具體的狀態值如下:
static final int CANCELLED =1;
static final int SIGNAL= -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
下面解釋下這些節點狀態的具體含義:
CANCELLED:因為超時或者中斷,結點會被設置為取消狀態,被取消狀態的結點不應該去競爭鎖,只能保持取消狀態不變,不能轉換為其他狀態。處于這種狀態的結點會被踢出隊列,被GC回收;
SIGNAL:表示這個結點的繼任結點被阻塞了,到時需要通知它;
CONDITION:表示這個結點在條件隊列中,因為等待某個條件而被阻塞;
PROPAGATE:使用在共享模式頭結點有可能牌處于這種狀態,表示鎖的下一次獲取可以無條件傳播;
0:None of the above,新結點會處于這種狀態。
了解了狀態,我們再來看看這個方法。
該方法有兩個參數,pred和node。
pred為當前節點的前趨節點。
node為當前節點。
該方法首先檢查前趨結點的waitStatus位,如果為SIGNAL,表示前趨結點會通知它,那么它可以放心大膽地掛起了;
如果waitStatus>0也就是前趨結點是一個被取消的結點怎么辦呢?那么就向前遍歷跳過被取消的結點,直到找到一個沒有被取消的結點為止,將找到的這個結點作為它的前趨結點,將找到的這個結點的waitStatus位設置為SIGNAL,返回false表示線程不應該被掛起。
如果不被掛起 , 那就會一直在acquireQueued方法的for循環里執行。
對于AQS,我自己也在讀代碼的階段,如果后面發現有什么不對的地方,或者有理解更透徹的地方,我會更新本章節,也歡迎大家評論指正。
參考資料: