本文基于java version "1.8.0_77"
ReentrantLock(java.util.concurrent.locks)(譯為:重入鎖)是java 5.0之后新加入的并發(fā)機(jī)制。他的出現(xiàn)并不是替代之前的內(nèi)置鎖synchronized,而是在內(nèi)置鎖不適用時(shí),可以提供更加靈活的加鎖機(jī)制。
ReentrantLock使用:
Lock lock = new ReentrantLock(); //可選參數(shù):boolean 是否公平
// lockInterruptibly(),可中斷的獲取鎖
// tryLock()//如果已經(jīng)被lock,則立即返回false不會(huì)等待,達(dá)到忽略操作的效果
// tryLock(long timeout, TimeUnit unit) //如果已經(jīng)被lock,嘗試等待,看是否可以獲得鎖,如果超市仍然無法獲得鎖則返回false繼續(xù)執(zhí)行
lock.lock(); / /如果被其它資源鎖定,會(huì)在此等待鎖釋放,達(dá)到暫停的效果
try {
//do something
}
finally {
lock.unlock(); // 不要漏掉
}
ReentrantLock的使用很簡(jiǎn)單,首尾加解鎖,中間是執(zhí)行同步代碼。ReentrantLock同樣是一種互斥,可重入鎖,同時(shí)它支持了獲取鎖的時(shí)候的公平性與否。
- 互斥:表示每次只有一個(gè)線程可以獲取鎖執(zhí)行同步代碼;
- 可重入:表示同一個(gè)線程可以對(duì)同一個(gè)鎖的重復(fù)獲取,比如在循環(huán)中加解鎖。
- 公平性:等待中的線程是否按照時(shí)間順序獲取鎖。
ReentrantLock與synchronized
ReentrantLock與synchronized相同,都是可重入鎖,互斥鎖。在提供了與synchronized相同的鎖功能的同時(shí),ReentrantLock還提供了如下功能:
- 鎖等待可以設(shè)置超時(shí)時(shí)間 tryLock(long, TimeUnit),如果取得成功則繼續(xù)處理,取得不成功,可以等下次運(yùn)行的時(shí)候處理,所以不容易產(chǎn)生死鎖。而synchronized則一旦進(jìn)入鎖請(qǐng)求要么成功,要么一直阻塞,所以更容易產(chǎn)生死鎖。此方法僅在調(diào)用時(shí)鎖為空閑狀態(tài)才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值true。如果鎖不可用,則此方法將立即返回值false。
- 可以設(shè)置可中斷的鎖等待 lockInterruptibly()
- 可以設(shè)置鎖等待的公平性 ReentrantLock(boolean fair)
需要注意的地方:lock 必須在 finally 塊中釋放。否則,如果受保護(hù)的代碼將拋出異常,鎖就有可能永遠(yuǎn)得不到釋放!
Lock
ReentrantLock實(shí)現(xiàn)了Lock接口,Lock接口定義了如下方法:
-
void lock()獲取鎖,可等待 -
void lockInterruptibly() throws InterruptedException可中斷的獲取鎖 -
boolean tryLock();如果已經(jīng)被lock,則立即返回false不會(huì)等待,達(dá)到忽略操作的效果 -
boolean tryLock(long, TimeUnit) throws InterruptedException;如果已經(jīng)被lock,嘗試等待,看是否可以獲得鎖,如果超時(shí)仍然無法獲得鎖則返回false繼續(xù)執(zhí)行 -
void unlock();解鎖 -
Condition newCondition()譯為:“條件”,創(chuàng)建一個(gè)新的Condition,綁定到當(dāng)前Lock上。Lock 替代了 synchronized 方法和語句的使用,而Condition 替代了 Object 監(jiān)視器方法的使用。在Condition中,await()相當(dāng)于wait(),signal()相當(dāng)于notify(),signalAll()相當(dāng)于notifyAll()。
Fuck源碼
我們從ReentrantLock調(diào)用的角度來看一下源碼:
看一下ReentrantLock的類結(jié)構(gòu)

我們翻看ReentrantLock的源碼可以看到,ReentrantLock看起來更像是一個(gè)代理類,他的所有對(duì)外公布的方法全部都是有兩個(gè)內(nèi)部類來實(shí)現(xiàn)的:FairSync(公平鎖)和NonfairSync(不公平鎖),對(duì)于是否為公平鎖,使用了策略模式,針對(duì)公平與否,使用不同的獲取鎖的策略。而這兩個(gè)類繼承了同一個(gè)類:Sync。而Sync類繼承了AbstractQueuedSynchronizer類。
我們?cè)谏掀?a href="http://www.itdecent.cn/p/4a6d4ed88b1d" target="_blank">AQS源碼分析中講到,如果線程獲取鎖失敗,則進(jìn)入等待隊(duì)列(FIFO),進(jìn)入隊(duì)列是按照時(shí)間順序的。
AQS中有兩個(gè)重要部分:
- 等待隊(duì)列:是為了管理等待的線程;
-
state系方法如下圖,由子類調(diào)用,用來控制是否允許獲取鎖。例如:ReentrantLock中用它來表示所有線程呢個(gè)已經(jīng)重復(fù)獲取該鎖的次數(shù),Semaphore用它來表示剩余的許可數(shù)量,F(xiàn)utureTask用它來表示任務(wù)的狀態(tài)(未開始,正在運(yùn)行,已結(jié)束,已取消等)。這樣子類不用關(guān)心等待隊(duì)列如何工作,只需要控制state就可以實(shí)現(xiàn)一個(gè)lock。

非公平鎖&重入
NonfairSync:tryAcquire(int acquires)

看到非公平鎖的tryAcquire方法調(diào)用了nonfairTryAcquire方法,我們繼續(xù)往下看:

假設(shè)一個(gè)線程A嘗試獲取鎖,首先判斷當(dāng)前state的值,
- 如果為0,則表示當(dāng)前并沒有線程獲取當(dāng)前鎖,那就嘗試將state設(shè)置為1,如果失敗,則表示已經(jīng)有線程獲取了鎖,線程A獲取失敗,返回false;如果成功,表示當(dāng)前線程A獲取了鎖,并保存線程A,設(shè)置標(biāo)記一下當(dāng)前獨(dú)占模式的線程。
- 如果不為0,表示已經(jīng)有線程獲取過鎖。這時(shí)有兩種可能:有可能是線程A已經(jīng)獲取過鎖,此時(shí)是線程A的重入;也有可能是其他線程(反正不是線程A)獲取鎖。這是需要先判斷一下是否是重入(
if (current == getExclusiveOwnerThread())),如果是重入情況下,則繼續(xù)在state的值的基礎(chǔ)上+1,這時(shí)重入,再次獲取了鎖。
公平鎖&重入
首先,看公平鎖的lock源碼:
FairSync:tryAcquire()

可以看到,與上面講到的非公平鎖極其類似,唯一不同的是,在首次獲取鎖的時(shí)候,進(jìn)行了hasQueuedPredecessors()判斷,判斷等待隊(duì)列中是否還有比當(dāng)前線程更早的, 如果為空,或者當(dāng)前線程線程是等待隊(duì)列的第一個(gè)時(shí)才占有鎖。我們看一下它的源碼:

這是對(duì)同步隊(duì)列中當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)的判斷,如果該方法返回true,則表示有線程比當(dāng)前線程更早地請(qǐng)求獲取鎖,因此需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。
這里判斷了(s = h.next) == null是為了,防止此時(shí)有另外的線程在同時(shí)搶占鎖,并獲取鎖,故如果為null,則進(jìn)入等待隊(duì)列。