AbstractQueuedSynchronizer源碼解讀

文章出處 http://www.cnblogs.com/micrari/p/6937995.html

1. 背景

AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是Doug Lea大師創作的用來構建鎖或者其他同步組件(信號量、事件等)的基礎框架類。JDK中許多併發工具類的內部實現都依賴於AQS,如ReentrantLock, Semaphore, CountDownLatch等等。學習AQS的使用與源碼實現對深入理解concurrent包中的類有很大的幫助。
本文重點介紹AQS中的基本實現思路,包括獨佔鎖、共享鎖的獲取和釋放實現原理和一些代碼細節。

對於AQS中ConditionObject的相關實現,可以參考我的另一篇博文AbstractQueuedSynchronizer源碼解讀--續篇之Condition

2. 簡介

AQS的主要使用方式是繼承它作爲一個內部輔助類實現同步原語,它可以簡化你的併發工具的內部實現,屏蔽同步狀態管理、線程的排隊、等待與喚醒等底層操作。

AQS設計基於模板方法模式,開發者需要繼承同步器並且重寫指定的方法,將其組合在併發組件的實現中,調用同步器的模板方法,模板方法會調用使用者重寫的方法。

3. 實現思路

下面介紹下AQS具體實現的大致思路。

AQS內部維護一個CLH隊列來管理鎖。
線程會首先嚐試獲取鎖,如果失敗,則將當前線程以及等待狀態等信息包成一個Node節點加到同步隊列裏。
接着會不斷循環嘗試獲取鎖(條件是當前節點爲head的直接後繼纔會嘗試),如果失敗則會阻塞自己,直至被喚醒;
而當持有鎖的線程釋放鎖時,會喚醒隊列中的後繼線程。

下面列舉JDK中幾種常見使用了AQS的同步組件:

  • ReentrantLock: 使用了AQS的獨佔獲取和釋放,用state變量記錄某個線程獲取獨佔鎖的次數,獲取鎖時+1,釋放鎖時-1,在獲取時會校驗線程是否可以獲取鎖。
  • Semaphore: 使用了AQS的共享獲取和釋放,用state變量作爲計數器,只有在大於0時允許線程進入。獲取鎖時-1,釋放鎖時+1。
  • CountDownLatch: 使用了AQS的共享獲取和釋放,用state變量作爲計數器,在初始化時指定。只要state還大於0,獲取共享鎖會因爲失敗而阻塞,直到計數器的值爲0時,共享鎖才允許獲取,所有等待線程會被逐一喚醒。

3.1 如何獲取鎖

獲取鎖的思路很直接:

while (不滿足獲取鎖的條件) {
    把當前線程包裝成節點插入同步隊列
    if (需要阻塞當前線程)
        阻塞當前線程直至被喚醒
}
將當前線程從同步隊列中移除

以上是一個很簡單的獲取鎖的僞代碼流程,AQS的具體實現比這個複雜一些,也稍有不同,但思想上是與上述僞代碼契合的。
通過循環檢測是否能夠獲取到鎖,如果不滿足,則可能會被阻塞,直至被喚醒。

3.2 如何釋放鎖

釋放鎖的過程設計修改同步狀態,以及喚醒後繼等待線程:

修改同步狀態
if (修改後的狀態允許其他線程獲取到鎖)
    喚醒後繼線程

這只是很簡略的釋放鎖的僞代碼示意,AQS具體實現中能看到這個簡單的流程模型。

3.3 API簡介

通過上面的AQS大體思路分析,我們可以看到,AQS主要做了三件事情

  • 同步狀態的管理
  • 線程的阻塞和喚醒
  • 同步隊列的維護

下面三個protected final方法是AQS中用來訪問/修改同步狀態的方法:

  • int getState(): 獲取同步狀態

  • void setState(): 設置同步狀態

  • boolean compareAndSetState(int expect, int update):基於CAS,原子設置當前狀態

在自定義基於AQS的同步工具時,我們可以選擇覆蓋實現以下幾個方法來實現同步狀態的管理:

方法 描述
boolean tryAcquire(int arg) 試獲取獨佔鎖
boolean tryRelease(int arg) 試釋放獨佔鎖
int tryAcquireShared(int arg) 試獲取共享鎖
boolean tryReleaseShared(int arg) 試釋放共享鎖
boolean isHeldExclusively() 當前線程是否獲得了獨佔鎖

以上的幾個試獲取/釋放鎖的方法的具體實現應當是無阻塞的。

AQS本身將同步狀態的管理用模板方法模式都封裝好了,以下列舉了AQS中的一些模板方法:

方法 描述
void acquire(int arg) 獲取獨佔鎖。會調用tryAcquire方法,如果未獲取成功,則會進入同步隊列等待
void acquireInterruptibly(int arg) 響應中斷版本的acquire
boolean tryAcquireNanos(int arg,long nanos) 響應中斷+帶超時版本的acquire
void acquireShared(int arg) 獲取共享鎖。會調用tryAcquireShared方法
void acquireSharedInterruptibly(int arg) 響應中斷版本的acquireShared
boolean tryAcquireSharedNanos(int arg,long nanos) 響應中斷+帶超時版本的acquireShared
boolean release(int arg) 釋放獨佔鎖
boolean releaseShared(int arg) 釋放共享鎖
Collection getQueuedThreads() 獲取同步隊列上的線程集合

上面看上去很多方法,其實從語義上來區分就是獲取和釋放,從模式上區分就是獨佔式和共享式,從中斷相應上來看就是支持和不支持。

 

源碼解讀  請參考http://www.cnblogs.com/micrari/p/6937995.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章