Lock、ReentrantLock和AbstractQueuedSynchronizer的源碼要點分析整理

前面已經說了不少Java併發和線程安全的東西,也提到並對比了內在鎖和J.U.C包(java.util.concurrent包,後同)中Lock的鎖。從這篇開始,對Java併發的整理從理論進入「實踐」階段,本篇對Lock、ReentrantLock和AbstractQueuedSynchronizer源碼作簡要分析和整理。先從Lock這個interface提及,而後分析ReentrantLock和AQS的實現。 java

0. 咱們先看下Lock接口和ReentrantLock的大致實現。下面是去掉JavaDoc相關注釋的代碼: node

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}


能夠看得出來,Lock能作這樣幾件事: 安全

  • 常規地得到鎖
  • 得到可中斷的鎖
  • 嘗試性得到鎖,非阻塞
  • 嘗試性得到鎖,若是超時則返回
  • 解鎖
  • 生成和當前鎖相關的條件(隊列)對象

再來看下ReentrantLock的狀況: 數據結構

  • 類聲明,以下實現了Lock和Serializable接口

public class ReentrantLock implements Lock, java.io.Serializable
  • 屬性,主要的屬性只有內部類Sync的對象屬性sync,ReentrantLock類的操做實際上都落在了sync身上
  • 構造方法,有重載實現的兩個,單參數的方法參數的含義爲是否爲公平鎖,方法的實現就是構造一個Sync對象(根據公平參數,肯定是FairSync仍是NonfairSync)並賦值給sync,默認構造方法會調用參數爲false的方法
  • lock()和newCondition()調用sync的同名方法
  • lockInterruptibly()調用sync的acquireInterruptibly(1)
  • tryLock()調用sync的nonfairTryAcquire(1)
  • 等待性tryLock()調用sync的tryAcquireNanos(1, unit.toNanos(timeout))
  • unlock()調用sync的release(1)

除此以外,ReentrantLock是可重入鎖,還有一些支持可重入的方法,這裏不細說。能夠說ReetrantLock是基於它的內部類Sync的對象來實現的,接下來看下Sync的類層次結構: 併發

Sync類結構

從eclipse中看,類層次結構一目瞭然,Sync被FairSync和Nonfair擴展,而父層有AbstactOwnableSynchronizer和AbstractQueuedSynchronizer。前者實現了當前同步器被哪一個線程佔有的邏輯,實現了get/setExclusiveOwnerThread()方法,肯定獲取當前互斥鎖的Thread對象。後者則是java.util.concurrent包中很是重要的類,下面就重點來講說這個AbstractQueuedSynchronizer(AQS)。 eclipse

1. AQS的隊列結構和隊列節點 ui

從AbstractQueuedSynchronizer的名字就能夠看得出來,這個類是抽象的隊列的同步器。同步器不用說了;有關抽象的,以及具體如何和擴展的子類配合實現加鎖和解鎖,後面那段會具體描述;這裏咱們看看AQS的比較重要比較核心的部分,也就是狀態處理和隊列的實現。 spa

從AQS類在eclipse的outline中,能夠看出,除了序列化和具體的Unsafe底層操做相關的東西,AQS有三個最重要的屬性和兩個內部類: 線程

  • private volatile int state
  • private transient volatile Node head
  • private transient volatile Node tail
  • static final class Node
  • public class ConditionObject implements Condition, java.io.Serializable

其中state是當前的鎖狀態,一般(至少ReentrantLock是這麼用的)這是鎖是否被佔用的一個重要標誌,在ReentrantLock實現中是得到鎖的重入線程數,0的時候是沒有線程佔用這個鎖的。而和AQS實例綁定(就是非靜態的內部類)的ConditionObject類是與條件對列相關的對象,後面細說。剩下的最重要就是Node靜態內部類,也是構成隊列的主要數據結構。其實此Node實現也並不複雜,就是一般的雙向鏈表結構,有指向先後節點的引用,除此以外就是鏈表節點的數據部分,有以下屬性字段: code

  • volatile int waitStatus。當前節點的狀態,主要表示當前線程是得到鎖、等待鎖、在等待隊列中等狀態,對應於Node類中的幾個常量
  • volatile Thread thread。當前節點對應的線程對象。
  • Node nextWaiter。AQS爲每一個條件對象單獨維護了一個等待隊列,依靠的就是這個屬性引用。

在鎖隊列維護上,其實是雙向的。每次建立新節點,以當前線程爲數據,nextWaiter指向互斥常量或共享常量。新增結點時,獲取tail,並設置新節點的prev爲tail,並嘗試原子操做設置新節點爲tail節點,若是tail結點爲空或者設置tail結點出問題則調用enq方法循環嘗試,其中爲空 狀態時,則new一個空Node爲head,並讓tail=head。

出隊列的操做其實是和線程相關的,在阻塞等待得到鎖的過程當中或者是執行condition的await()時,調用acquireQueued()方法,循環比較當前線程結點的上一個結點是否是head並調用tryAcquire()。若是成功,則設置當前node爲head,並解除當前node向前以及前一個結點指向當前node的引用(設置爲null),這樣前一個結點就失去了引用鏈上的引用。第一次出隊列的是首次初始化隊列時建立的空Node對象,後面依次是以前被解鎖的線程對應的node。固然,若是tryAcquire()不成功,則會將判斷當前node的狀態,若是是0則設置爲SIGNAL常量並用LockSupport的park()方法掛起當前線程。

2. Sync和AQS的配合以及ReentrantLock的lock()和unlock()實現。
前面簡單說到過,ReentrantLock的lock方法調用了sync的lock()方法,而不論是公平實現(FairSync)仍是非公平實現(NonfairSync),所作的主要工做都是調用AQS的acquire()方法。而unlock()方法更直接,調用的是AQS的release()方法。

更進一步,對於acquire()和release()方法,所作的大概操做有兩樣,一個是調用名字爲try開頭的方法,即tryAcquire()和tryRelease()等,此外就是作隊列和線程相關的操做。而對於AQS,有以下五個方法是未完整實現,須要擴展的子類進行定義的:

  • protected boolean tryAcquire(int arg)
  • protected boolean tryRelease(int arg)
  • protected int tryAcquireShared(int arg)
  • protected boolean tryReleaseShared(int arg)
  • protected boolean isHeldExclusively()

結合ReentrantLock及其內部類Sync(以NonfairSync爲例)的實現,主要是tryAcquire()和tryRelease(),咱們看下如何構造鎖操做。

當加鎖時,調用acquire()方法,acquire()會嘗試原子操做tryAcquire()。這個方法在非公平實現中,主要是經過AQS的state來檢查和維護鎖狀態,若是state是0,說明沒有線程佔有這個鎖,若是不爲0而且鎖的佔有線程是當前線程,則是重入的狀況,都可以得到鎖並修改state值。若是是首次得到鎖,則設置鎖佔有線程爲當前線程。固然,若是前面兩種狀況都不知足,說明嘗試得到鎖失敗,須要作前面段落所述的隊列操做,建立一個等待結點並進入循環,循環中的park()調用掛起當前線程。

當解鎖時,作對應而相反的操做。release()調用tryRelease()方法,若是修改state值成功,則找到隊列中應該喚起的結點,對節點中的線程調用unpark()方法,恢復線程執行。這個操做在被恢復執行線程acquireQueued()方法的循環中完成,釋放頭結點並返回是否中斷的狀態,繼續執行。

3. Lock的五個特色方面:嘗試性非阻塞得到鎖可中斷、時間調度、公平性、一對多。

下面在簡單介紹下ReentrantLock比起內在的synchronized鎖的一些優秀特色的實現:

  • 基於Unsafe的原子操做來修改state的狀態,不管成功失敗都會直接返回,這保證了非阻塞方式嘗試得到鎖
  • 可中斷和容許時間調度,則是利用了Unsafe的park方法的特性,park掉的線程是能夠響應中斷被喚醒的,而park的帶有時間參數的重載方法則保證了時間調度性
  • 公平和非公平實現,這個是在ReentrantLock的Sync的子類中實現的,主要的區別就是公平鎖保證了隊列的第一個節點先得到鎖,而非公平不保證這點
  • 至於一對多,貌似也沒什麼可多說的,一個類中能夠有多個ReentrantLock類對象屬性,天然就能夠有多個鎖,每一個對象單獨維護一個state屬性

4. Condition的實現。

至於條件隊列的實現,前文也多少提到了一些。AQS有個實現了Condition接口的內部類ConditionObject,其複用了鎖隊列的Node結點,單獨爲每一個條件維護了一個單向鏈表隊列。

當await()時,建立一個狀態爲CONDITION常量的Node類結點,釋放當前線程的鎖,並進入一個循環。這個循環退出的條件是結點已經被放到鎖隊列上或者是檢測到了中斷作中斷處理,循環的內容就是不斷的去park()掉當前線程。當循環退出後嘗試從新得到鎖,以繼續執行等待後的代碼。

而signal()/signalll()方法更好理解,主要操做就是將一個或者多個Node對象的狀態設置爲0,並將該節點加入獲取鎖的隊列中,恢復線程。

本文對java.util.concurrent.locks的可重入鎖機制和AQS進行了比較詳細的分析,後續也有可能會對ReentrantReadWriteLock和Semaphore作分析。更詳細的邏輯還請參照JDK的源碼。

相關文章
相關標籤/搜索