ReentrantLock是JDK提供的一個可重入互斥鎖,所謂可重入就是同一個鎖允許被已經獲得該鎖的線程重新獲得。可重入鎖的好處可以在遞歸算法中使用鎖,不可重入鎖則導致無法在遞歸算法中使用鎖。因爲第二次遞歸時由於第一次遞歸已經佔有鎖,而導致死鎖。本文我們將探討JDK中ReentrantLock的實現。
Semaphore是JDK提供的一個可共享的同步組建,有n個許可,多個線程可以共同去獲得許可,當線程申請的許可小於n時即可成功申請,否則申請失敗。
AQS(AbstractQueuedSynchronizer)是Java實現同步組建的基礎框架,一般以靜態內部類的形式實現在某個同步組件類中,通過代理的方式向外提供同步服務,ReentrantLock和Semaphore都是基於AQS實現的同步組件,前者是獨佔式同步組建,即一個線程獲得後,其他線程無法獲得。後者是共享式同步組件,一個線程獲得後,在滿足的條件下,其他線程也可以獲得。
AQS工作原理
AQS是Java實現同步組建的基礎框架,其基本思想是用一個volatile int state變量來表示當前同步組件的狀態,用getState()獲取同步組件的狀態,用compareAndSet(int expect, int update)來對state狀態進行操作,compareAndSet可以保證對state變量更新值的原子性。AQS中很多方法是final的,即不允許用戶覆蓋,用戶自定義的方法一般有:
- tryAcquire: 獨佔式獲取同步狀態,該函數一般首先查詢state的值,如果state不允許繼續被獲取,直接返回false。如果state允許繼續被獲取,CAS嘗試更新state的值,成功返回true,失敗返回false
- tryAcquireShared:共享式的獲取同步狀態,該一般是在CAS死循環獲取state的值,計算state被獲取後的值,如果該值爲負數,直接返回負數表示失敗,如果該值爲正值,則用CAS更新該值,當CAS更新失敗時,重複上述步驟,直至返回負數或CAS更新成功返回正值。
- tryRelease:獨佔式的釋放同步狀態
- tryReleaseShared:共享式的釋放同步狀態,一般在CAS死循環中反覆嘗試,直至釋放成功
- isHeldExclusively:判斷當前同步器是否被當前線程佔有
AQS提供的模板方法有:
- acquire:獨佔式的獲取同步狀態,獲取成功則返回,獲取失敗則會進入等待隊列,該方法會調用用戶自定義的tryAcquire函數
- acquireInterruptibly:與acquire類似,不同在於當進入等待隊列時,遇到中斷會拋出InterruptedException異常,用戶可以處理該中斷異常
- tryAcquireNanos:在acquireInterruptibly的基礎上增加了時間限制,一定時間內沒有成功獲取則返回false
- acquireShared:共享式的獲取同步狀態,成功則返回,失敗則進入等待隊列,該方法會調用用戶自定義的tryAcquired函數
- acquireSharedInterruptibly:在等待隊列可以相應中斷,與上類似
- tryAcquireShared:在acquireSharedInterruptibly增加了超時限制
- release:獨佔式的釋放同步狀態,會調用用戶自定義tryRelease函數
- releaseShared:共享式的釋放同步狀態,會調用用戶自定義tryReleaseShared函數
- getQueuedThreads:獲取等待隊列線程集合
ReentrantLock源碼分析
ReentrantLock的默認構造函數是
1
2
3
|
public
ReentrantLock() { sync
= new
NonfairSync(); } |
NonfairSync繼承了Sync,Sync是一個抽象類,並繼承了抽象類AbstractQueuedSynchronizer。
ReentrantLock是一個獨佔式的鎖,所以它需要實現tryAcquire函數和tryRelease函數
tryAcquire函數源碼如下
1
2
3
|
protected
final
boolean
tryAcquire( int
acquires) { return
nonfairTryAcquire(acquires); } |
nonfairTryAcquire(acquires)源碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
final
boolean
nonfairTryAcquire( int
acquires) { final
Thread current = Thread.currentThread(); int
c = getState(); if
(c == 0 )
{ if
(compareAndSetState( 0 ,
acquires)) { setExclusiveOwnerThread(current); return
true ; } } else
if
(current == getExclusiveOwnerThread()) { int
nextc = c + acquires; if
(nextc < 0 )
//
overflow throw
new
Error( "Maximum
lock count exceeded" ); setState(nextc); return
true ; } return
false ; } |
- 先得到當前線程
- 查詢當前state值,如果爲0則說明當前鎖還未被其他線程獲取,則嘗試CAS獲得鎖,成功則把佔有鎖的線程設置爲當前線程,返回true。失敗返回false。
- 如果state不爲0則說明該鎖已經被其他線程獲取,則檢查獲得鎖的線程是否是當前線程以實現可重入特性,如果是,則更新state的值,並返回true。此處更新不需要CAS,因爲只有當前線程可以操作state。
- 其他情況返回false
tryRelease函數源碼如下
1
2
3
4
5
6
7
8
9
10
11
12
|
protected
final
boolean
tryRelease( int
releases) { int
c = getState() - releases; if
(Thread.currentThread() != getExclusiveOwnerThread()) throw
new
IllegalMonitorStateException(); boolean
free = false ; if
(c == 0 )
{ free
= true ; setExclusiveOwnerThread( null ); } setState(c); return
free; } |
- 首先得到釋放之後的狀態值c
- 檢查當前釋放鎖的線程,如果不是已佔有鎖的線程則拋出異常,因爲ReentrantLock是獨佔式鎖,釋放鎖的線程一定是佔有鎖的線程
- 如果c是等於0的,說明獲取鎖的所有函數都已經返回,則鎖釋放成功
- 如果c不等於0,說明只是部分遞歸的函數返回,部分遞歸函數還未返回,則釋放失敗,鎖依然被佔有
Lock函數源碼
1
2
3
|
public
void
lock() { sync.lock(); } |
sunc的lock函數
1
2
3
4
5
6
|
final
void
lock() { if
(compareAndSetState( 0 ,
1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire( 1 ); } |
該函數首先直接嘗試CAS操作,成功則設置當前函數爲佔有鎖的函數,返回,失敗則調用acquire函數。acquire函數爲AQS實現的模板方法,它嘗試獲得鎖,成功則返回,不成功則進入等待隊列直至獲取成功。
unLock函數源碼
1
2
3
|
public
void
unlock() { sync.release( 1 ); } |
調用tryRelease函數釋放鎖。
Semaphore的源碼
Semaphore構造函數如下:
1
2
3
|
public
Semaphore( int
permits) { sync
= new
NonfairSync(permits); } |
與ReentrantLock代碼結構非常相似。Semaphore是一個共享式的同步組建,它應該實現tryAcquireShared和tryReleaseShared
tryAcquireShared函數源碼:
1
2
3
|
protected
int
tryAcquireShared( int
acquires) { return
nonfairTryAcquireShared(acquires); } |
nonfairTryAcquireShared源碼:
1
2
3
4
5
6
7
8
9
|
final
int
nonfairTryAcquireShared( int
acquires) { for
(;;) { int
available = getState(); int
remaining = available - acquires; if
(remaining < 0
|| compareAndSetState(available,
remaining)) return
remaining; } } |
- 函數首先取得當前的可用許可數,並計算被獲取acquires個許可後剩餘的許可數。
- 如果剩餘的許可數小於0直接返回剩餘的許可數,即負值
- 如果大於0則嘗試使用CAS循環更新state的值,更新失敗則重試上述步驟,直至返回負值更新失敗,或者返回非負值更新成功。
tips:與獨佔式的tryAcquire邏輯不太一樣,獨佔式的tryAcquire在CAS操作失敗後,直接返回失敗。本人覺得共享式的tryAcquiredShared在CAS操作失敗後,因爲組件是共享的,所以再次嘗試獲取同步組件成功的可能性較大,所以在CAS失敗後,嘗試再次更新。而獨佔式的CAS更新失敗後,組件已經被其他線程獲取,再次嘗試成功的可能性較小,所以沒有重新嘗試。純屬個人觀點。
tryReleasedShared源碼
1
2
3
4
5
6
7
8
9
10
|
protected
final
boolean
tryReleaseShared( int
releases) { for
(;;) { int
current = getState(); int
next = current + releases; if
(next < current) //
overflow throw
new
Error( "Maximum
permit count exceeded" ); if
(compareAndSetState(current, next)) return
true ; } } |
- 函數計算釋放後的state值並驗證是否溢出
- CAS更新state的值直至成功
acquire函數源碼
1
2
3
|
public
void
acquire() throws
InterruptedException { sync.acquireSharedInterruptibly( 1 ); } |
acquireSharedInterruptibly爲AQS提供的模板方法,調用了tryAcquireShared,成功直接返回,不成功加入等待隊列,並添加了處理中斷的機制
release函數源碼
1
2
3
|
public
void
release() { sync.releaseShared( 1 ); } |
releaseShared爲AQS提供的模板方法,調用了tryReleaseShared
總結
完整的ReentrantLock和Semaphore實現非常複雜,本文旨在介紹AQS框架,並通過ReentrantLock和Semaphore一個獨佔式的同步組件和一個非獨佔式的同步組件來學習怎麼使用AQS實現通組件,具體來說分爲以下步驟:
- 待實現的同步組件是獨佔式的還是共享式的
- 獨佔式的同步組件實現tryAcquire和tryRelease,非獨佔式的實現tryAcquireShared和tryReleaseShared
- 將我們實現的同步組建相應的方法如Lock和unLock代理到AQS對應的函數包括用戶自定義的函數和AQS提供的模板函數
AQS的方便之處在於我們只需要實現tryAcquire和tryRelease或tryAcquireShared和tryReleaseShared就可以使用AQS幫我們實現好的阻塞的acquire函數,可中斷的acquire函數,帶超時的acquire函數等模板函數,大大簡化了用戶的開發量和難度。