文章出處 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() | 獲取同步隊列上的線程集合 |
上面看上去很多方法,其實從語義上來區分就是獲取和釋放,從模式上區分就是獨佔式和共享式,從中斷相應上來看就是支持和不支持。