??在面試或者日常開發(fā)當(dāng)中盼玄,經(jīng)常會遇到公平鎖和非公平鎖的概念。
兩者最大的區(qū)別如下??
1?? 公平鎖:N個線程去申請鎖時埃儿,會按照先后順序進(jìn)入一個隊列當(dāng)中去排隊童番,依次按照先后順序獲取鎖。就像下圖描述的上廁所的場景一樣剃斧,先來的先占用廁所幼东,后來的只能老老實實排隊。
2?? 非公平鎖:N個線程去申請鎖策橘,會直接去競爭鎖娜亿,若能獲取鎖就直接占有,獲取不到鎖沛婴,再進(jìn)入隊列排隊順序等待獲取鎖。同樣以排隊上廁所打比分泻蚊,這時候丑婿,后來的線程會先嘗試插隊看看能否搶占到廁所,若能插隊搶占成功秒旋,就能使用廁所诀拭,若失敗就得老老實實去隊伍后面排隊耕挨。
針對這兩個概念,我們通過ReentrantLock底層源碼來分析下?? :公平鎖和非公平鎖在ReentrantLock類當(dāng)中鎖怎樣實現(xiàn)的贪庙。
??ReentrantLock內(nèi)部實現(xiàn)的公平鎖類是FairSync赋铝,非公平鎖類是NonfairSync。
當(dāng)ReentrantLock以無參構(gòu)造器創(chuàng)建對象時,默認(rèn)生成的是非公平鎖對象NonfairSync良哲,只有帶參且參數(shù)為true的情況下FairSync助隧,才會生成公平鎖,若傳參為false時巍实,生成的依然是非公平鎖哩牍,兩者構(gòu)造器源碼結(jié)果如下??
圖1
在實際開發(fā)當(dāng)中膝昆,關(guān)于ReentrantLock的使用案例叠必,一般是這個格式??
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock();
// block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
這時的lock指向的其實是NonfairSync對象纬朝,即非公平鎖骄呼。
當(dāng)使用lock.lock()對臨界區(qū)進(jìn)行占鎖操作時,最終會調(diào)用到NonfairSync對象的lock()方法俄讹。根據(jù)圖1可知绕德,NonfairSync和FairSync兩者的lock方法實現(xiàn)邏輯是不一樣的,而體現(xiàn)其鎖是否符合公平與否的地方踪蹬,就是在兩者的lock方法里臣咖。
可以看到夺蛇,在非公平鎖NonfairSync的上鎖lock方法當(dāng)中,若if(compareAndSetState(0,1))判斷不滿足娶聘,就會執(zhí)行acquire(1)方法甚脉,該方法跟公平鎖FairSync的lock方法里調(diào)用的acquire(1)其實是同一個,但方法里的tryAcquire具體實現(xiàn)又存在略微不同狡耻,這里后面會討論猴凹。
這里就呼應(yīng)前文提到的非公平鎖的概念——當(dāng)N個線程去申請非公平鎖郊霎,它們會直接去競爭鎖,若能獲取鎖就直接占有歹篓,獲取不到鎖,再進(jìn)入隊列排隊順序等待獲取鎖背捌。這里的“獲取不到鎖毡庆,再進(jìn)入隊列排隊順序等待獲取鎖”可以理解成?——當(dāng)線程過來直接競爭鎖失敗后,就會變成公平鎖的形式毅否,進(jìn)入到一個隊列當(dāng)中蝇刀,按照先后順序排隊去獲取鎖。
而if(compareAndSetState(0,1))語句塊的邏輯捆探,恰好就體現(xiàn)了“當(dāng)N個線程去申請非公平鎖站粟,它們會直接去競爭鎖,若能獲取鎖就直接占有”這句話的意思助被。
??首先切诀,先來分析NonfairSync的lock()方法原理趾牧,源碼如下??
final void lock() {
//先競爭鎖,若能競爭成功,則占有鎖資源
if (compareAndSetState(0, 1))
//將獨占線程成員變量設(shè)置為當(dāng)前線程蹦渣,表示占有鎖資源的線程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
compareAndSetState(0, 1)就是一個當(dāng)前線程與其他線程搶占鎖的過程柬唯,這里面涉及到AQS的知識點,因此锄奢,閱讀本文時,需具備一定的AQS基礎(chǔ)涂屁。
JUC的鎖實現(xiàn)是基于AQS實現(xiàn)的拆又,可以簡單理解成,AQS里定義了一個private volatile int state變量栈源,若state值為0竖般,說明無線程占有,其他線程可以進(jìn)行搶占鎖資源艰亮;若state值為1胞谭,說明已有線程占有鎖資源丈屹,其他線程需要等待該占有鎖的線程釋放鎖資源后,方能進(jìn)行搶占鎖的動作彩库。
線程在搶占鎖時先蒋,是通過CAS對state變量進(jìn)行置換操作的,期望值expect是0眯搭,更新值update為1业岁,若期望值expect能與內(nèi)存地址里的state值一致笔时,就可以通過原子操作將內(nèi)存地址里state值置換成更新值update,返回true借笙,反之,就置換失敗返回false盗痒。
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
可見盼忌,這里的if (compareAndSetState(0, 1))就體現(xiàn)了非公平鎖的機制,當(dāng)前線程會先去競爭鎖看成,若能競爭成功跨嘉,就占有鎖資源祠乃。
若競爭鎖失敗話,就會執(zhí)行acquire(1)方法琴拧,其原理就相當(dāng)走跟公平鎖類似的邏輯嘱支。
acquire(1);
進(jìn)入acquire方法,該方法是位于AbstractQueuedSynchronizer里沛膳,就是前文提到的AQS汛聚,即抽象同步隊列器倚舀,它相當(dāng)提供一套用戶態(tài)層面的鎖框架,基于它可以實現(xiàn)用戶態(tài)層面的鎖機制话速。
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
注意一點芯侥,NonfairSync和FairSync調(diào)用的acquire(int arg)方法中的tryAcquire方法柱查,其實現(xiàn)是不同的。NonfairSync調(diào)用的acquire方法研乒,其底層tryAcquire調(diào)用的是NonfairSync重寫的tryAcquire方法淋硝;FairSync調(diào)用的acquire方法,其底層tryAcquire調(diào)用的是FairSync重寫的tryAcquire方法竿报。
NonfairSync類的acquire方法的流程圖如下??
先分析非公平鎖的!tryAcquire(arg)底層源碼實現(xiàn)烈菌,該方法的整體邏輯是芽世,通過getState()獲取state狀態(tài)值诡壁,判斷是否已為0。若state等于0了旺矾,說明此時鎖資源處于無鎖狀態(tài)纽帖,那么懊直,當(dāng)前線程就可以直接再執(zhí)行一遍CAS原子搶鎖操作,若CAS成功雕崩,說明已成功搶占鎖融撞。若state不為0,再判斷當(dāng)前線程是否與占有資源的鎖為同一個線程饶火,若同一個線程,那么就進(jìn)行重入鎖操作当辐,即ReentrantLock支持同一個線程對資源的重復(fù)加鎖鲤看,每次加鎖义桂,就對state值加1,解鎖時袖裕,就對state解鎖罢浇,直至減到0最后釋放鎖。
??最后攒岛,若在該方法里灾锯,通過CAS搶占鎖成功或者重入鎖成功嗅榕,那么就會返回true,若失敗兼雄,就會返回false帽蝶。
final boolean nonfairTryAcquire(int acquires) {
//獲取當(dāng)前線程引用
final Thread current = Thread.currentThread();
//獲取AQS的state狀態(tài)值
int c = getState();
//若state等于0了励稳,說明鎖處于無被占用狀態(tài),可被當(dāng)前線程搶占
if (c == 0) {
//再次嘗試通過CAS搶鎖
if (compareAndSetState(0, acquires)) {
//將獨占線程成員變量設(shè)置為當(dāng)前線程趣避,表示占有鎖資源的線程
setExclusiveOwnerThread(current);
return true;
}
}
//判斷當(dāng)前線程是否與占有鎖資源的線程為同一個線程
else if (current == getExclusiveOwnerThread()) {
//每次重入鎖程帕,state就會加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
在 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))代碼當(dāng)中,根據(jù) &&短路機制澎羞,若!tryAcquire(arg)為false敛苇,就不會再執(zhí)行后面代碼枫攀。反之株茶,若!tryAcquire(arg)為true,說明搶占鎖失敗了或者不屬于重入鎖蹦掐,那么就會繼續(xù)后續(xù)acquireQueued(addWaiter(Node.EXCLUSIVE), arg))代碼的執(zhí)行僵闯。acquireQueued里面的邏輯鳖粟,就可以理解成“獲取不到鎖,再進(jìn)入隊列排隊順序等待獲取鎖”泳秀。這塊內(nèi)容涉及比較復(fù)雜的雙向鏈表邏輯榄攀,我后面會另外寫一篇文章深入分析檩赢,本文主要是講解公平鎖和非公平鎖的區(qū)別科普。
FairSync公平鎖lock方法里acquire(1)的邏輯與非公平鎖NonfairSync的acquire(1)很類似币他,其底層實現(xiàn)同樣是這樣??
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我們來看下FairSync類里重實現(xiàn)的tryAcquire與NonfairSync最終執(zhí)行的tryAcquire區(qū)別??
可以看到蝴悉,公平鎖FairSync的tryAcquire方法比NonfairSync的nonfairTryAcquire方法多了一行!hasQueuedPredecessors()代碼瘾敢。
在FairSync公平鎖里,若hasQueuedPredecessors()返回false射众,那么!hasQueuedPredecessors()就會為true晃财,在執(zhí)行以下判斷時,就會通過compareAndSetState(0, acquires)即CAS原子搶占鎖罗洗。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
那么伙菜,什么情況下命迈,hasQueuedPredecessors() 能得到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;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
?存在兩種情況:
1?? 第一種情況,h != t為false踊淳,說明head和tail節(jié)點都為null或者h(yuǎn)和t都指向一個假節(jié)點head陕靠,這兩種情況都說明了剪芥,此時的同步隊列還沒有初始化,簡單點理解溉躲,就是在當(dāng)前線程之前益兄,還沒有出現(xiàn)線程去搶占鎖净捅,因此,此時荆永,鎖是空閑的, 同時當(dāng)前線程算上最早到來的線程之一(高并發(fā)場景下同一時刻可能存在N個線程同時到來)豆村,就可以通過CAS競爭鎖骂删。
2?? 第二種情況桃漾,h != t為true但(s = h.next) == null || s.thread != Thread.currentThread()為false,當(dāng)頭節(jié)點head和尾節(jié)點都不為空且指向不是同一節(jié)點,就說明同步隊列已經(jīng)初始化恋追,此時至少存在兩個以上節(jié)點罚屋,那么head.next節(jié)點必定不為空脾猛,即(s = h.next) == null會為false,若s.thread != Thread.currentThread()為false羹铅,說明假節(jié)點head的next節(jié)點剛好與當(dāng)前線程為同一節(jié)點愉昆,也就意味著跛溉,當(dāng)前線程排在隊列最前面,排在前面的可以在鎖空閑時獲取鎖資源专肪,就可以執(zhí)行compareAndSetState(0, acquires)去搶占鎖資源堪侯。
若同步隊列已經(jīng)初始化抖格,且當(dāng)前線程又不是在假節(jié)點head的next節(jié)點咕晋,就只能老老實實去后面排隊等待獲取鎖了收奔。