本文基于java version "1.8.0_77"
ReentrantLock(java.util.concurrent.locks)(譯為:重入鎖)是java 5.0之后新加入的并發機制。他的出現并不是替代之前的內置鎖synchronized,而是在內置鎖不適用時,可以提供更加靈活的加鎖機制。
ReentrantLock使用:
Lock lock = new ReentrantLock(); //可選參數:boolean 是否公平
// lockInterruptibly(),可中斷的獲取鎖
// tryLock()//如果已經被lock,則立即返回false不會等待,達到忽略操作的效果
// tryLock(long timeout, TimeUnit unit) //如果已經被lock,嘗試等待,看是否可以獲得鎖,如果超市仍然無法獲得鎖則返回false繼續執行
lock.lock(); / /如果被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果
try {
//do something
}
finally {
lock.unlock(); // 不要漏掉
}
ReentrantLock的使用很簡單,首尾加解鎖,中間是執行同步代碼。ReentrantLock同樣是一種互斥,可重入鎖,同時它支持了獲取鎖的時候的公平性與否。
- 互斥:表示每次只有一個線程可以獲取鎖執行同步代碼;
- 可重入:表示同一個線程可以對同一個鎖的重復獲取,比如在循環中加解鎖。
- 公平性:等待中的線程是否按照時間順序獲取鎖。
ReentrantLock與synchronized
ReentrantLock與synchronized相同,都是可重入鎖,互斥鎖。在提供了與synchronized相同的鎖功能的同時,ReentrantLock還提供了如下功能:
- 鎖等待可以設置超時時間 tryLock(long, TimeUnit),如果取得成功則繼續處理,取得不成功,可以等下次運行的時候處理,所以不容易產生死鎖。而synchronized則一旦進入鎖請求要么成功,要么一直阻塞,所以更容易產生死鎖。此方法僅在調用時鎖為空閑狀態才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值true。如果鎖不可用,則此方法將立即返回值false。
- 可以設置可中斷的鎖等待 lockInterruptibly()
- 可以設置鎖等待的公平性 ReentrantLock(boolean fair)
需要注意的地方:lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!
Lock
ReentrantLock實現了Lock接口,Lock接口定義了如下方法:
-
void lock()
獲取鎖,可等待 -
void lockInterruptibly() throws InterruptedException
可中斷的獲取鎖 -
boolean tryLock();
如果已經被lock,則立即返回false不會等待,達到忽略操作的效果 -
boolean tryLock(long, TimeUnit) throws InterruptedException;
如果已經被lock,嘗試等待,看是否可以獲得鎖,如果超時仍然無法獲得鎖則返回false繼續執行 -
void unlock();
解鎖 -
Condition newCondition()
譯為:“條件”,創建一個新的Condition,綁定到當前Lock上。Lock 替代了 synchronized 方法和語句的使用,而Condition 替代了 Object 監視器方法的使用。在Condition中,await()相當于wait(),signal()相當于notify(),signalAll()相當于notifyAll()。
Fuck源碼
我們從ReentrantLock調用的角度來看一下源碼:
看一下ReentrantLock的類結構
我們翻看ReentrantLock的源碼可以看到,ReentrantLock看起來更像是一個代理類,他的所有對外公布的方法全部都是有兩個內部類來實現的:FairSync(公平鎖)和NonfairSync(不公平鎖),對于是否為公平鎖,使用了策略模式,針對公平與否,使用不同的獲取鎖的策略。而這兩個類繼承了同一個類:Sync。而Sync類繼承了AbstractQueuedSynchronizer類。
我們在上篇的AQS源碼分析中講到,如果線程獲取鎖失敗,則進入等待隊列(FIFO),進入隊列是按照時間順序的。
AQS中有兩個重要部分:
- 等待隊列:是為了管理等待的線程;
-
state
系方法如下圖,由子類調用,用來控制是否允許獲取鎖。例如:ReentrantLock中用它來表示所有線程呢個已經重復獲取該鎖的次數,Semaphore用它來表示剩余的許可數量,FutureTask用它來表示任務的狀態(未開始,正在運行,已結束,已取消等)。這樣子類不用關心等待隊列如何工作,只需要控制state就可以實現一個lock。
非公平鎖&重入
NonfairSync:tryAcquire(int acquires)
看到非公平鎖的tryAcquire方法調用了nonfairTryAcquire方法,我們繼續往下看:
假設一個線程A嘗試獲取鎖,首先判斷當前state的值,
- 如果為0,則表示當前并沒有線程獲取當前鎖,那就嘗試將state設置為1,如果失敗,則表示已經有線程獲取了鎖,線程A獲取失敗,返回false;如果成功,表示當前線程A獲取了鎖,并保存線程A,設置標記一下當前獨占模式的線程。
- 如果不為0,表示已經有線程獲取過鎖。這時有兩種可能:有可能是線程A已經獲取過鎖,此時是線程A的重入;也有可能是其他線程(反正不是線程A)獲取鎖。這是需要先判斷一下是否是重入(
if (current == getExclusiveOwnerThread())
),如果是重入情況下,則繼續在state的值的基礎上+1,這時重入,再次獲取了鎖。
公平鎖&重入
首先,看公平鎖的lock源碼:
FairSync:tryAcquire()
可以看到,與上面講到的非公平鎖極其類似,唯一不同的是,在首次獲取鎖的時候,進行了hasQueuedPredecessors()
判斷,判斷等待隊列中是否還有比當前線程更早的, 如果為空,或者當前線程線程是等待隊列的第一個時才占有鎖。我們看一下它的源碼:
這是對同步隊列中當前節點是否有前驅節點的判斷,如果該方法返回true,則表示有線程比當前線程更早地請求獲取鎖,因此需要等待前驅線程獲取并釋放鎖之后才能繼續獲取鎖。
這里判斷了(s = h.next) == null
是為了,防止此時有另外的線程在同時搶占鎖,并獲取鎖,故如果為null,則進入等待隊列。