ReentrantLock,可重入鎖,是一種遞歸無阻塞的同步機制。它可以等同于synchronized的使用,但是ReentrantLock提供了比synchronized更強大、靈活的鎖機制,可以減少死鎖發(fā)生的概率。
API介紹如下:
一個可重入的互斥鎖定 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監(jiān)視器鎖定相同的一些基本行為和語義,但功能更強大。ReentrantLock 將由最近成功獲得鎖定,并且還沒有釋放該鎖定的線程所擁有。當(dāng)鎖定沒有被另一個線程所擁有時,調(diào)用 lock 的線程將成功獲取該鎖定并返回。如果當(dāng)前線程已經(jīng)擁有該鎖定,此方法將立即返回??梢允褂?isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發(fā)生。
ReentrantLock還提供了公平鎖和非公平鎖的選擇,構(gòu)造方法接受一個可選的公平參數(shù)(默認非公平鎖),當(dāng)設(shè)置為true時,表示公平鎖,否則為非公平鎖。
公平鎖與非公平鎖的區(qū)別在于公平鎖的鎖獲取是有順序的。但是公平鎖的效率往往沒有非公平鎖的效率高,在許多線程訪問的情況下,公平鎖表現(xiàn)出較低的吞吐量。
獲取鎖
我們一般都是這么使用ReentrantLock獲取鎖的:
//非公平鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();
然后看看lock()
public void lock() {
sync.lock();
}
Sync為ReentrantLock里面的一個內(nèi)部類,它繼承AQS(AbstractQueuedSynchronizer),它有兩個子類:公平鎖FairSync和非公平鎖NonfairSync。
ReentrantLock里面大部分的功能都是委托給Sync來實現(xiàn)的,同時Sync內(nèi)部定義了lock()抽象方法由其子類去實現(xiàn),默認實現(xiàn)了nonfairTryAcquire(int acquires)方法,可以看出它是非公平鎖的默認實現(xiàn)方式。下面我們看非公平鎖的lock()方法:
final void lock() {
//嘗試獲取鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//獲取失敗,調(diào)用AQS的acquire(int arg)方法
acquire(1);
}
首先會第一次嘗試快速獲取鎖,如果獲取失敗,則調(diào)用acquire(int arg)方法,該方法定義在AQS中,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這個方法首先調(diào)用tryAcquire(int arg)方法,在AQS中講述過,tryAcquire(int arg)需要自定義同步組件提供實現(xiàn),非公平鎖實現(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)同樣是需要自定義同步組件自己實現(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)徹底釋放后該方法才會返回true。當(dāng)state == 0 時,則將鎖持有線程設(shè)置為null,free= true,表示釋放成功。
公平鎖與非公平鎖
公平鎖與非公平鎖的區(qū)別在于獲取鎖的時候是否按照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)的過程,會發(fā)現(xiàn)兩者唯一的區(qū)別就在于公平鎖在獲取同步狀態(tài)時多了一個限制條件:hasQueuedPredecessors(),定義如下:
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾節(jié)點
Node h = head; //頭節(jié)點
Node s;
//頭節(jié)點 != 尾節(jié)點
//同步隊列第一個節(jié)點不為null
//當(dāng)前線程是同步隊列第一個節(jié)點
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
該方法主要做一件事情:主要是判斷當(dāng)前線程是否位于CLH同步隊列中的第一個。如果是則返回true,否則返回false。
公平鎖和非公平鎖的區(qū)別
公平鎖每次獲取到鎖為同步隊列中的第一個節(jié)點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續(xù)獲取該鎖,則有可能導(dǎo)致其他線程永遠無法獲取到鎖,造成“饑餓”現(xiàn)象。
公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,ReentrantLock默認選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統(tǒng)更大的吞吐量。
ReentrantLock與synchronized的區(qū)別
ReentrantLock是Lock的實現(xiàn)類,是一個互斥的同步器,在多線程高競爭條件下,ReentrantLock比synchronized有更加優(yōu)異的性能表現(xiàn)。
- 用法比較
- Lock使用起來比較靈活,但是必須有釋放鎖的配合動作
- Lock必須手動獲取與釋放鎖,而synchronized不需要手動釋放和開啟鎖
- Lock只適用于代碼塊鎖,而synchronized可用于修飾方法、代碼塊等
- 特性比較
ReentrantLock的優(yōu)勢體現(xiàn)在:
具備嘗試非阻塞地獲取鎖的特性:當(dāng)前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖
能被中斷地獲取鎖的特性:與synchronized不同,獲取到鎖的線程能夠響應(yīng)中斷,當(dāng)獲取到鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放
超時獲取鎖的特性:在指定的時間范圍內(nèi)獲取鎖;如果截止時間到了仍然無法獲取鎖,則返回
3 注意事項
在使用ReentrantLock類的時,一定要注意三點:
在finally中釋放鎖,目的是保證在獲取鎖之后,最終能夠被釋放
不要將獲取鎖的過程寫在try塊內(nèi),因為如果在獲取鎖時發(fā)生了異常,異常拋出的同時,也會導(dǎo)致鎖無故被釋放。
ReentrantLock提供了一個newCondition的方法,以便用戶在同一鎖的情況下可以根據(jù)不同的情況執(zhí)行等待或喚醒的動作。