ReentrantLock,可重入鎖锋边,是一種遞歸無阻塞的同步機(jī)制拆内。它可以等同于synchronized的使用,但是ReentrantLock提供了比synchronized更強(qiáng)大宠默、靈活的鎖機(jī)制麸恍,可以減少死鎖發(fā)生的概率。
API介紹如下:
一個(gè)可重入的互斥鎖定 Lock搀矫,它具有與使用 synchronized 方法和語句所訪問的隱式監(jiān)視器鎖定相同的一些基本行為和語義抹沪,但功能更強(qiáng)大。ReentrantLock 將由最近成功獲得鎖定瓤球,并且還沒有釋放該鎖定的線程所擁有融欧。當(dāng)鎖定沒有被另一個(gè)線程所擁有時(shí),調(diào)用 lock 的線程將成功獲取該鎖定并返回卦羡。如果當(dāng)前線程已經(jīng)擁有該鎖定噪馏,此方法將立即返回÷潭可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發(fā)生欠肾。
ReentrantLock還提供了公平鎖
和非公平鎖
的選擇,構(gòu)造方法接受一個(gè)可選的公平參數(shù)(默認(rèn)非公平鎖)拟赊,當(dāng)設(shè)置為true時(shí)刺桃,表示公平鎖,否則為非公平鎖吸祟。
公平鎖與非公平鎖的區(qū)別在于公平鎖的鎖獲取是有順序的瑟慈。但是公平鎖的效率往往沒有非公平鎖的效率高,在許多線程訪問的情況下屋匕,公平鎖表現(xiàn)出較低的吞吐量葛碧。
獲取鎖
我們一般都是這么使用ReentrantLock獲取鎖的:
//非公平鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();
然后看看lock()
public void lock() {
sync.lock();
}
Sync為ReentrantLock里面的一個(gè)內(nèi)部類,它繼承AQS(AbstractQueuedSynchronizer)过吻,它有兩個(gè)子類:公平鎖FairSync和非公平鎖NonfairSync进泼。
ReentrantLock里面大部分的功能都是委托給Sync來實(shí)現(xiàn)的,同時(shí)Sync內(nèi)部定義了lock()抽象方法由其子類去實(shí)現(xiàn),默認(rèn)實(shí)現(xiàn)了nonfairTryAcquire(int acquires)方法缘琅,可以看出它是非公平鎖的默認(rèn)實(shí)現(xiàn)方式粘都。下面我們看非公平鎖的lock()方法:
final void lock() {
//嘗試獲取鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//獲取失敗廓推,調(diào)用AQS的acquire(int arg)方法
acquire(1);
}
首先會(huì)第一次嘗試快速獲取鎖刷袍,如果獲取失敗,則調(diào)用acquire(int arg)方法樊展,該方法定義在AQS中呻纹,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這個(gè)方法首先調(diào)用tryAcquire(int arg)方法,在AQS中講述過专缠,tryAcquire(int arg)需要自定義同步組件提供實(shí)現(xiàn)雷酪,非公平鎖實(shí)現(xiàn)如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//當(dāng)前線程
final Thread current = Thread.currentThread();
//獲取同步狀態(tài)
int c = getState();
//state == 0,表示沒有該鎖處于空閑狀態(tài)
if (c == 0) {
//獲取鎖成功,設(shè)置為當(dāng)前線程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//線程重入
//判斷鎖持有的線程是否為當(dāng)前線程
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;
}
該方法主要邏輯:首先判斷同步狀態(tài)state == 0 ?涝婉,如果是表示該鎖還沒有被線程持有哥力,直接通過CAS獲取同步狀態(tài),如果成功返回true墩弯。如果state != 0吩跋,則判斷當(dāng)前線程是否為獲取鎖的線程,如果是則獲取鎖渔工,成功返回true锌钮。成功獲取鎖的線程再次獲取鎖,這是增加了同步狀態(tài)state引矩。
釋放鎖
獲取同步鎖后梁丘,使用完畢則需要釋放鎖,ReentrantLock提供了unlock釋放鎖:
public void unlock() {
sync.release(1);
}
unlock內(nèi)部使用Sync的release(int arg)釋放鎖旺韭,release(int arg)是在AQS中定義的:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
與獲取同步狀態(tài)的acquire(int arg)方法相似氛谜,釋放同步狀態(tài)的tryRelease(int arg)同樣是需要自定義同步組件自己實(shí)現(xiàn):
protected final boolean tryRelease(int releases) {
//減掉releases
int c = getState() - releases;
//如果釋放的不是持有鎖的線程,拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//state == 0 表示已經(jīng)釋放完全了区端,其他線程可以獲取同步狀態(tài)了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
只有當(dāng)同步狀態(tài)徹底釋放后該方法才會(huì)返回true混蔼。當(dāng)state == 0 時(shí),則將鎖持有線程設(shè)置為null珊燎,free= true惭嚣,表示釋放成功。
公平鎖與非公平鎖
公平鎖與非公平鎖的區(qū)別在于獲取鎖的時(shí)候是否按照FIFO的順序來悔政。釋放鎖不存在公平性和非公平性晚吞,上面以非公平鎖為例,下面我們來看看公平鎖的tryAcquire(int arg):
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
比較非公平鎖和公平鎖獲取同步狀態(tài)的過程谋国,會(huì)發(fā)現(xiàn)兩者唯一的區(qū)別就在于公平鎖在獲取同步狀態(tài)時(shí)多了一個(gè)限制條件:hasQueuedPredecessors()槽地,定義如下:
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾節(jié)點(diǎn)
Node h = head; //頭節(jié)點(diǎn)
Node s;
//頭節(jié)點(diǎn) != 尾節(jié)點(diǎn)
//同步隊(duì)列第一個(gè)節(jié)點(diǎn)不為null
//當(dāng)前線程是同步隊(duì)列第一個(gè)節(jié)點(diǎn)
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
該方法主要做一件事情:主要是判斷當(dāng)前線程是否位于CLH同步隊(duì)列中的第一個(gè)。如果是則返回true,否則返回false捌蚊。
公平鎖和非公平鎖的區(qū)別
公平鎖每次獲取到鎖為同步隊(duì)列中的第一個(gè)節(jié)點(diǎn)集畅,保證請(qǐng)求資源時(shí)間上的絕對(duì)順序,而非公平鎖有可能剛釋放鎖的線程下次繼續(xù)獲取該鎖缅糟,則有可能導(dǎo)致其他線程永遠(yuǎn)無法獲取到鎖挺智,造成“饑餓”現(xiàn)象。
公平鎖為了保證時(shí)間上的絕對(duì)順序窗宦,需要頻繁的上下文切換赦颇,而非公平鎖會(huì)降低一定的上下文切換,降低性能開銷赴涵。因此媒怯,ReentrantLock默認(rèn)選擇的是非公平鎖,則是為了減少一部分上下文切換髓窜,保證了系統(tǒng)更大的吞吐量扇苞。
ReentrantLock與synchronized的區(qū)別
ReentrantLock是Lock的實(shí)現(xiàn)類,是一個(gè)互斥的同步器寄纵,在多線程高競爭條件下鳖敷,ReentrantLock比synchronized有更加優(yōu)異的性能表現(xiàn)。
- 用法比較
- Lock使用起來比較靈活擂啥,但是必須有釋放鎖的配合動(dòng)作
- Lock必須手動(dòng)獲取與釋放鎖哄陶,而synchronized不需要手動(dòng)釋放和開啟鎖
- Lock只適用于代碼塊鎖,而synchronized可用于修飾方法哺壶、代碼塊等
- 特性比較
ReentrantLock的優(yōu)勢(shì)體現(xiàn)在:
具備嘗試非阻塞地獲取鎖的特性:當(dāng)前線程嘗試獲取鎖屋吨,如果這一時(shí)刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖
能被中斷地獲取鎖的特性:與synchronized不同山宾,獲取到鎖的線程能夠響應(yīng)中斷至扰,當(dāng)獲取到鎖的線程被中斷時(shí),中斷異常將會(huì)被拋出资锰,同時(shí)鎖會(huì)被釋放
超時(shí)獲取鎖的特性:在指定的時(shí)間范圍內(nèi)獲取鎖敢课;如果截止時(shí)間到了仍然無法獲取鎖,則返回
3 注意事項(xiàng)
在使用ReentrantLock類的時(shí)绷杜,一定要注意三點(diǎn):
在finally中釋放鎖直秆,目的是保證在獲取鎖之后,最終能夠被釋放
不要將獲取鎖的過程寫在try塊內(nèi)鞭盟,因?yàn)槿绻讷@取鎖時(shí)發(fā)生了異常圾结,異常拋出的同時(shí),也會(huì)導(dǎo)致鎖無故被釋放齿诉。
ReentrantLock提供了一個(gè)newCondition的方法筝野,以便用戶在同一鎖的情況下可以根據(jù)不同的情況執(zhí)行等待或喚醒的動(dòng)作晌姚。