重入鎖ReentrantLock险掀,顧名思義,就是支持重進(jìn)入的鎖艇抠,它表示該鎖能夠支持一個(gè)線程對資源的重復(fù)加鎖。除此之外炭剪,該鎖的還支持獲取鎖時(shí)的公平和非公平性選擇练链。
閱讀這個(gè)可重入鎖類之前翔脱,可以先閱讀我的上兩篇文章奴拦,對lock以及AbstractQueuedSynchronizer這兩個(gè)類的作用和設(shè)計(jì)有一個(gè)基礎(chǔ)的了解。然后再看著個(gè)類的時(shí)候届吁,會更好的理解错妖。
AQS(AbstractQueuedSynchronizer)隊(duì)列同步器源碼閱讀(二)
http://www.reibang.com/p/e0066f9349cd
AQS(AbstractQueuedSynchronizer)隊(duì)列同步器源碼閱讀(一)
http://www.reibang.com/p/a41088fc1516
然后我們可以知道一般來說,ReentrantLock是一個(gè)可重入鎖疚沐,所以會實(shí)現(xiàn)Lock這個(gè)類暂氯,并重寫該類提供的幾個(gè)方法:
//獲取鎖,釋放鎖
void lock();
void unlock();
//可相應(yīng)線程中斷的獲取鎖亮蛔,當(dāng)線程被中斷后痴施,鎖會被釋放。而一般的lock則不會響應(yīng)
void lockInterruptibly() throws InterruptedException;
//嘗試獲取鎖究流,獲取不到則返回false
boolean tryLock();
//與上一個(gè)一樣
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//獲取對應(yīng)的condition辣吃,后面會專門研究,現(xiàn)在只要知道芬探,這個(gè)可以用來配合完成不同線程的等待/通知機(jī)制神得。
Condition newCondition();
然后一般重寫以上幾個(gè)方法時(shí),需要依賴隊(duì)列同步器來獲取同步狀態(tài)偷仿,獲取不到需要加入同步隊(duì)列等等哩簿,所以一般還會設(shè)計(jì)工具類Sync繼承AbstractQueuedSynchronizer
繼承AQS的子類重寫以上AQS提供的方法后酝静,對外ReentrantLock是實(shí)現(xiàn)Lock提供的幾個(gè)方法一般是調(diào)用Sync繼承的AQS里面的方法节榜。例如:
加鎖時(shí):
//調(diào)用的是AQS的子類sync的lock
public void lock() {
sync.lock();
}
//而lock方法一般會根據(jù)具體的鎖的設(shè)計(jì)去實(shí)現(xiàn)我們已經(jīng)重寫的acquire方法。大體的鎖的設(shè)計(jì)不會偏差太多别智。
然后我們來具體看一下:
ReentrantLock 是一個(gè)可重入鎖宗苍。
關(guān)于線程的調(diào)度策略分為公平調(diào)度和非公平調(diào)度。
他的設(shè)計(jì)同樣是內(nèi)部有一個(gè)繼承AQS的靜態(tài)內(nèi)部類Sync亿遂,同時(shí)為了區(qū)分不同的調(diào)度策略浓若,有設(shè)計(jì)了兩個(gè)子類繼承Sync渺杉,
static final class NonfairSync extends Sync{.....}
static final class FairSync extends Sync{.....}
看類名可以看出,一個(gè)是針對非公平調(diào)度策略設(shè)計(jì)的鎖挪钓,一個(gè)是公平調(diào)度的是越。兩種重寫同步隊(duì)列器的方法的實(shí)現(xiàn)不同。
再來看構(gòu)造方法:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
這回應(yīng)該不用多解釋了吧碌上,看類名倚评。所以說可重入鎖是默認(rèn)非公平調(diào)度策略的,就是說線程執(zhí)行的順序是不是按執(zhí)行時(shí)間順序執(zhí)行的馏予,是非公平的天梧,效率比較高。
可重入性
可重入鎖顧名思義就是線程獲取到鎖之后霞丧,可以再次獲取該鎖呢岗,而不會被鎖阻塞。該鎖實(shí)現(xiàn)需要解決兩個(gè)問題:
1.線程再次獲取鎖蛹尝。鎖需要去識別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程后豫,如果是,則再次成功獲取突那。
2.鎖的最終釋放挫酿。。線程重復(fù)n次獲取了鎖愕难,隨后在第n次釋放該鎖后早龟,其他線程能夠獲取到該鎖。
以默認(rèn)的非公平調(diào)度的鎖實(shí)現(xiàn)來查看獲取同步狀態(tài)時(shí)猫缭,如何處理:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//獲取當(dāng)前state葱弟,重入數(shù)量
int c = getState();
//如果還未有線程獲取該鎖
if (c == 0) {
//獲取同步狀態(tài)成功,則設(shè)置當(dāng)前線程為持有鎖的線程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果已有線程獲取該鎖饵骨,且該線程為已持有鎖的線程
else if (current == getExclusiveOwnerThread()) {
//設(shè)置重入的數(shù)量翘悉。并返回獲取鎖成功
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
該方法增加了再次獲取同步狀態(tài)的處理邏輯:通過判斷當(dāng)前線程是否為獲取鎖的線程來決定獲取操作是否成功,如果是獲取鎖的線程再次請求居触,則將同步狀態(tài)值進(jìn)行增加并返回true妖混,表示獲取同步狀態(tài)成功。
成功獲取鎖的線程再次獲取鎖轮洋,只是增加了同步狀態(tài)值制市,這也就要求ReentrantLock在釋放同步狀態(tài)時(shí)減少同步狀態(tài)值。
protected final boolean tryRelease(int releases) {
//釋放時(shí)減去同步狀態(tài)值
int c = getState() - releases;
//如果當(dāng)前線程與持有線程不一致弊予,則報(bào)對象頭狀態(tài)異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果同步狀態(tài)值為0祥楣,代表該鎖已全部釋放,需要釋放鎖,使其他線程能夠獲取該鎖
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
如果該鎖被獲取了n次误褪,那么前(n-1)次tryRelease(int releases)方法必須返回false责鳍,而只有同步狀態(tài)完全釋放了,才能返回true兽间±穑可以看到,該方法將同步狀態(tài)是否為0作為最終釋放的條件嘀略,當(dāng)同步狀態(tài)為0時(shí)恤溶,將占有線程設(shè)置為null,并返回true帜羊,表示釋放成功咒程。
對于非公平鎖,只要CAS設(shè)置同步狀態(tài)成功讼育,則表示當(dāng)前線程獲取了鎖帐姻,而公平鎖則不同.
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//主要區(qū)別如下: 即加入了同步隊(duì)列中當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)的判斷,如果該方法返回true窥淆,
//則表示有線程比當(dāng)前線程更早地請求獲取鎖卖宠,因此需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。
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;
非公平調(diào)度策略加鎖的流程
final void lock() {
//1.如果同步操作state忧饭,獲取同步狀態(tài)成功,則設(shè)置當(dāng)前線程為當(dāng)前獨(dú)占式獲取鎖.否則進(jìn)行獲取筷畦。
//compareAndSetState(0, 1) 使用的是:
//unsafe.compareAndSwapInt(this, stateOffset, expect, update); CAS偏移地址來直接修改state的值词裤。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 2.否則獲取同步狀態(tài)。進(jìn)去里面看實(shí)現(xiàn)
acquire(1);
}
//1.產(chǎn)時(shí)獲取同步狀態(tài)鳖宾,(tryAcquire),
失敗則加入隊(duì)尾tail吼砂,狀態(tài)設(shè)置為EXCLUSIVE,(addWaiter(Node.EXCLUSIVE), arg))
同時(shí)進(jìn)入自旋去獲取同步狀態(tài)鼎文,直到該節(jié)點(diǎn)前一個(gè)節(jié)點(diǎn)為頭節(jié)點(diǎn)并獲取成功渔肩,則出隊(duì)列,并喚醒下一個(gè)節(jié)點(diǎn)拇惋,并且響應(yīng)中斷周偎。(acquireQueued()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()看上面的分析.主要調(diào)用我們NonfairSync重寫的nonfairTryAcquire。
如果獲取非公平的可重入鎖失敗撑帖,則執(zhí)行下面方法蓉坎。該方法不做具體分析了,之前已經(jīng)有具體分析過了胡嘿。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 1. 獲得當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)
final Node p = node.predecessor();
// 2. 當(dāng)前節(jié)點(diǎn)能否獲取獨(dú)占式鎖
// 2.1 如果當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲取同步狀態(tài)蛉艾,即可以獲得獨(dú)占式鎖
if (p == head && tryAcquire(arg)) {
//隊(duì)列頭指針用指向當(dāng)前節(jié)點(diǎn)
setHead(node);
//釋放前驅(qū)節(jié)點(diǎn)
p.next = null; // help GC
failed = false;
return interrupted;
}
// 2.2 獲取鎖失敗,線程進(jìn)入等待狀態(tài)等待獲取獨(dú)占式鎖
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
非公平調(diào)度策略釋放鎖的流程就不再說了,上面有勿侯。
然后本來想要再分享下拓瞪,公平調(diào)度獲取鎖和解鎖的過程,其實(shí)不用助琐。解鎖的過程是一致的吴藻,這個(gè)前面有說了。然后加鎖的區(qū)別也是只有一點(diǎn)區(qū)別弓柱。就是獲取同步狀態(tài)的時(shí)候沟堡,需要判斷前面是否有頭節(jié)點(diǎn),如果沒有則可以直接獲取同步狀態(tài)矢空,否則繼續(xù)自旋航罗。因?yàn)楣秸{(diào)度是根據(jù)時(shí)間線程執(zhí)行的時(shí)間順序獲取鎖的,所以通過控制這點(diǎn)屁药,來判斷是否按時(shí)間進(jìn)行獲取同步狀態(tài)粥血,從而控制獲取鎖的順序。
從上面### 標(biāo)記的那行開始看酿箭,以下代碼的邏輯與nonfairTryAcquire基本上一直复亏,唯一的不同在于增加了hasQueuedPredecessors的邏輯判斷,方法名就可知道該方法用來判斷當(dāng)前節(jié)點(diǎn)在同步隊(duì)列中是否有前驅(qū)節(jié)點(diǎn)的判斷缭嫡,如果有前驅(qū)節(jié)點(diǎn)說明有線程比當(dāng)前線程更早的請求資源缔御,根據(jù)公平性,當(dāng)前線程請求資源失敗妇蛀。如果當(dāng)前節(jié)點(diǎn)沒有前驅(qū)節(jié)點(diǎn)的話耕突,再才有做后面的邏輯判斷的必要性。公平鎖每次都是從同步隊(duì)列中的第一個(gè)節(jié)點(diǎn)獲取到鎖评架,而非公平性鎖則不一定眷茁,有可能剛釋放鎖的線程能再次獲取到鎖。
公平鎖 VS 非公平鎖
1.公平鎖每次獲取到鎖為同步隊(duì)列中的第一個(gè)節(jié)點(diǎn)纵诞,保證請求資源時(shí)間上的絕對順序上祈,而非公平鎖有可能剛釋放鎖的線程下次繼續(xù)獲取該鎖,則有可能導(dǎo)致其他線程永遠(yuǎn)無法獲取到鎖浙芙,造成“饑餓”現(xiàn)象登刺。
2.公平鎖為了保證時(shí)間上的絕對順序,需要頻繁的上下文切換茁裙,而非公平鎖會降低一定的上下文切換塘砸,降低性能開銷。因此晤锥,ReentrantLock默認(rèn)選擇的是非公平鎖掉蔬,則是為了減少一部分上下文切換廊宪,保證了系統(tǒng)更大的吞吐量。
編寫不易女轿,給個(gè)贊