概述
重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖吐辙,它表示能夠支持一個線程對資源的重復(fù)加鎖宣决。除此之外,該鎖還支持獲取鎖時的公平和非公平選擇昏苏。Synchronized關(guān)鍵字通過獲取自增尊沸、釋放遞減的方式來隱式的支持重入,那么Reentrant是如何支持重入的呢贤惯?又是怎么實現(xiàn)公平和非公平選擇的呢洼专?接下來我們帶著這些問題來看ReentrantLock的源碼
重進入的實現(xiàn)原理
重進入是指任意線程在獲取到鎖之后,能夠再次獲取該鎖孵构,而不會因為再次獲取該鎖被阻塞屁商,該特性的實現(xiàn)主要需要解決的是一下兩個問題:
- 線程再次獲取鎖。鎖需要識別來嘗試獲取鎖的線程颈墅,是不是當前占有鎖的線程棒假,如果是,那么獲取成功精盅,如果不是,那么獲取失敗谜酒。
- 鎖的最終釋放叹俏。線程重復(fù)n次獲取了鎖,隨后在n次釋放該鎖后僻族,其他線程能夠正常獲取到鎖粘驰。鎖最終能夠正常釋放屡谐,要求鎖的獲取進行自增計數(shù),計數(shù)表示該鎖被重復(fù)獲取的次數(shù)蝌数,而鎖被釋放時愕掏,計數(shù)自減,當計數(shù)歸零時顶伞,表示該鎖已經(jīng)成功釋放饵撑。
ReentrantLock是通過組合自定義同步器來實現(xiàn)鎖的獲取和釋放的,下面我們以默認的非公平鎖源碼來深入分析下實現(xiàn)原理唆貌,非公平鎖獲取鎖的源碼如下:
final boolean nonfairTryAcquire(int acquires) {
//獲取當前線程
final Thread current = Thread.currentThread();
//獲取當前同步狀態(tài)
int c = getState();
//如果同步狀態(tài)等于0滑潘,說明該鎖未被任何線程占用
if (c == 0) {
//CAS方法修改同步狀態(tài)
if (compareAndSetState(0, acquires)) {
//將當前線程賦值給exclusiveOwnerThread
setExclusiveOwnerThread(current);
return true;
}
}
//否則,判斷占有該鎖的線程是不是當前線程
else if (current == getExclusiveOwnerThread()) {
//再次獲取锨咙,狀態(tài)值加上acquires
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
相信大家很容易看懂這段代碼语卤,該方法增加了重入鎖再次獲取同步狀態(tài)的邏輯:判斷占有鎖的線程是否為當前線程來決定獲取操作是否成功,如果是獲取鎖的線程再次請求酪刀,那么將同步狀態(tài)值增加并返回true粹舵,來表示獲取同步狀態(tài)成功。成功獲取鎖的線程再次獲取鎖骂倘,只是增加了同步狀態(tài)值眼滤,這也就要求ReentrantLock在釋放同步狀態(tài)的時候,減少同步狀態(tài)的值稠茂,其對應(yīng)的tryRelease()方法源碼如下:
protected final boolean tryRelease(int releases) {
//同步狀態(tài)遞減
int c = getState() - releases;
//如果當前線程不是占有該鎖的線程柠偶,那么拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果遞減后同步狀態(tài)為0,那么將釋放鎖睬关,返回true
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//如果狀態(tài)不為0诱担,那么返回false
setState(c);
return free;
}
從代碼中我們可以看出,只有同步狀態(tài)為0的時候电爹,鎖才會被完全釋放蔫仙。如果該鎖被獲取了n次,那么前(n-1)次tryRelease(int releases)方法必須返回false丐箩,只有最后一次才會返回true摇邦。
公平鎖與非公平鎖
ReentrantLock支持兩種鎖:公平鎖與非公平鎖,分別對應(yīng)實現(xiàn)為FairSync和NonfairSync屎勘。公平性與否是針對獲取鎖而言的施籍,如果一個鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請求的絕對時間順序概漱,也就是FIFO丑慎。 回顧上一小節(jié)中非公平鎖獲取的nonfairTryAcquire(int acquires)方法,只要CAS自旋獲取同步狀態(tài)成功,則表示當前線程獲取了鎖竿裂,不會要求FIFO玉吁,而公平鎖則不然。ReentrantLock的構(gòu)造方法為無參構(gòu)造方法時腻异,構(gòu)造的就是非公平鎖进副,源碼為:
public ReentrantLock() {
sync = new NonfairSync();
}
而ReentrantLock還有另外一種構(gòu)造方法,有參構(gòu)造方法:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
當傳入?yún)?shù)為true時悔常,創(chuàng)建公平鎖影斑;否則創(chuàng)建非公平鎖。下面我們來看下公平鎖的處理邏輯是怎么樣的这嚣,核心方法如下:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果當前同步狀態(tài)為0
if (c == 0) {
//判斷同步隊列中當前節(jié)點前面是否還有其余節(jié)點鸥昏,如果沒有,那么嘗試獲取同步狀態(tài)姐帚,成功則返回true
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//否則吏垮,判斷占有鎖的線程是否為當前線程,如果是罐旗,那么同步狀態(tài)加1膳汪,返回true。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//否則返回false
return false;
}
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//判斷同步隊列中九秀,當前節(jié)點前面是否還有其余節(jié)點
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
從上面的源碼中我們看到遗嗽,nonfailSync中的tryAcquire方法,多調(diào)用了hasQueuedPredecessors()方法來判斷同步隊列中鼓蜒,當前節(jié)點前是否還有其余節(jié)點痹换。如果有,則說明有線程比當前線程更早請求資源都弹,根據(jù)公平性要求娇豫,當前節(jié)點獲取資源失敗畅厢;如果當前節(jié)點沒有前驅(qū)節(jié)點冯痢,才會做后續(xù)操作。
公平鎖和非公平鎖可以總結(jié)出一下幾點:
- 公平鎖保證了鎖的獲取按照FIFO原則框杜,每次獲取到鎖的都是同步隊列的第一個節(jié)點浦楣,保證了請求資源時間上的絕對順序;非公平鎖咪辱,有可能剛剛釋放鎖的線程立馬又獲取到鎖振劳,而有的線程會一直獲取不到鎖,這也就可能造成“饑餓”現(xiàn)象油狂。
- 公平鎖為了保證請求資源上的絕對順序澎迎,需要頻繁的更換線程庐杨,切換上下文,而非公平鎖則一定程度上降低了上下文的切換夹供,降低了性能開銷。所以ReentrantLock默認選擇非公平鎖仁堪,這樣做是為了減少上下文切換哮洽,保證系統(tǒng)有更大的吞吐量。
注:本文參考《Java并發(fā)編程的藝術(shù)》