徹底弄懂ReentrantLock —— 超詳細(xì)的原碼分析
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
66套java從入門到精通實(shí)戰(zhàn)課程分享
ReentrantLock 是基于?AQS?框架實(shí)現(xiàn),JDK 中線程并發(fā)訪問的同步技術(shù),
它是一個(gè)互斥鎖,也叫獨(dú)占鎖,支持可重入,支持鎖的公平性。
大神Doug Lea寫的,整個(gè)AQS框架都是他一個(gè)人擼出來的,牛!
Doug Lea有多牛
就像:
談喜劇電影,不可能不提星爺
談相聲,繞不開郭德鋼
說中國教育,總得聊聊蔡元培
說到心理學(xué),一定得說弗洛伊德
說到并發(fā)編程,Doug Lea不得不提
java1.6 之前, synchronized效率太低了,大神Doug Lea就開發(fā)了AQS框架,就是解決并發(fā)問題。
JDK的出品公司SUN公司,一看,呀,這AQS性能那是相當(dāng)可以,好吧,那就吸收進(jìn)來,直接放JDK里吧。
synchronized可是SUN公司親生的呀,可性能太差,SUN公司琢磨,要不優(yōu)化下synchronized
于是乎,SUN公司的開發(fā)團(tuán)隊(duì),歷經(jīng)數(shù)年,優(yōu)化了synchronized,其性能大大提升,和AQS不相上下了。
從某種程度上說,Doug Lea 以一已之力,PK SUN公司整個(gè)開發(fā)團(tuán)隊(duì),牛人!
這里從ReentrantLock?加鎖解鎖機(jī)制,由淺入深,讓你徹底弄懂其原理。
一、應(yīng)用場景
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????CountDownLatch?downLatch?=?new?CountDownLatch(1);
????????for(int?i?=?0;?i?10;?i++){
????????????new?Thread(()?->?{
????????????????try?{
????????????????????downLatch.await();
????????????????????for(int?j?=?0;?j?1000;?j++){
????????????????????????total++;
????????????????????}
????????????????}?catch?(InterruptedException?e)?{
????????????????????e.printStackTrace();
????????????????}
????????????}).start();
????????}
????????Thread.sleep(2000);
????????downLatch.countDown();
????????Thread.sleep(2000);
????????System.out.println(total);
????}
這段代碼,起10個(gè)線程,每個(gè)線程加 1000 次,期望值是 10000,但因?yàn)椴l(fā),最后會(huì)小于 10000
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????CountDownLatch?downLatch?=?new?CountDownLatch(1);
????????ReentrantLock?lock?=?new?ReentrantLock();
????????for(int?i?=?0;?i?10;?i++){
????????????new?Thread(()?->?{
????????????????try?{
????????????????????downLatch.await();
????????????????????lock.lock();?//?加鎖
????????????????????for(int?j?=?0;?j?1000;?j++){
????????????????????????total++;
????????????????????}
????????????????????lock.unlock();?//?解鎖
????????????????}?catch?(InterruptedException?e)?{
????????????????????e.printStackTrace();
????????????????}
????????????}).start();
????????}
????????Thread.sleep(2000);
????????downLatch.countDown();
????????Thread.sleep(2000);
????????System.out.println(total);
????}
控制了并發(fā)訪問,算出來的結(jié)果就是 10000
二、什么是AQS
簡單講,AQS(AbstractQueuedSynchronizer),是大神Doug Lea寫的,一個(gè)同步框架。
JDK 中 java.util.concurrent 包下,很多處理并發(fā)的工具類都直接或間接引用了AQS,比如ReentrantLock,Semaphore,CountDownLatch……

AQS 這個(gè)抽象類中,有幾個(gè)重要屬性,咱先放在前面說一下。
?首先說下,AQS 里面的
內(nèi)部類Node的幾個(gè)屬性
volatile?int?waitStatus;??//?Node里,記錄狀態(tài)用的
volatile?Thread?thread;??//?Node里,標(biāo)識(shí)哪個(gè)線程
volatile?Node?prev;??//?前驅(qū)節(jié)點(diǎn)(這個(gè)Node的上一個(gè)是誰)
volatile?Node?next;?//?后繼節(jié)點(diǎn)(這個(gè)Node的個(gè)一個(gè)是誰)
?AQS 本身的屬性
private?transient?Thread?exclusiveOwnerThread;?//?標(biāo)識(shí)拿到鎖的是哪個(gè)線程
private?transient?volatile?Node?head;?//?標(biāo)識(shí)頭節(jié)點(diǎn)
private?transient?volatile?Node?tail;?//?標(biāo)識(shí)尾節(jié)點(diǎn)
private?volatile?int?state;?//?同步狀態(tài),為0時(shí),說明可以搶鎖
這里畫了張圖,很直觀,咱們照著圖來說

當(dāng)多個(gè)線程來競爭鎖的時(shí)候,若搶鎖失敗,則生成一個(gè)Node,與 Thread 綁定,進(jìn)入隊(duì)列,該線程就被阻塞。
當(dāng)鎖釋放時(shí),喚醒節(jié)點(diǎn)去搶鎖。
這個(gè)由Node對象組成的隊(duì)列,叫CLH隊(duì)列,是一個(gè)先進(jìn)先出的隊(duì)列,雙向指針。
三、ReentrantLock加鎖邏輯
這里以非公平鎖為例來說明,這里畫了個(gè)簡化的流程圖,先有個(gè)印象

ReentrantLock這個(gè)類,并沒有直接繼承AQS,而在ReentrantLock有一個(gè)內(nèi)部類,sync,它繼承了AQS。
ReentrantLock?lock?=?new?ReentrantLock();
//?當(dāng)執(zhí)行這段代碼時(shí),就會(huì)創(chuàng)建一個(gè)AQS?對象,其status是0,具體原碼如下
????public?ReentrantLock()?{
????????sync?=?new?NonfairSync();??//?默認(rèn)是非公平鎖
????}
lock.lock();?//?加鎖
執(zhí)行這行代碼時(shí),會(huì)涉及到?自旋、?CAS、?入隊(duì)、?阻塞,下面照著原始說邏輯,具體先不展開。
//?非公平鎖實(shí)現(xiàn)
?????final?void?lock()?{
?????????if?(compareAndSetState(0,?1))?//?成功將state?由0改為1的線程,直接就可以拿到鎖
?????????????setExclusiveOwnerThread(Thread.currentThread());
?????????else
?????????????acquire(1);?//?沒拿到鎖,做特殊處理
?????}
//?獲取獨(dú)占鎖
????public?final?void?acquire(int?arg)?{
????????if?(!tryAcquire(arg)?&&?acquireQueued(addWaiter(Node.EXCLUSIVE),?arg)){
?????????selfInterrupt();?//?在線程上打一個(gè)阻斷標(biāo)志
????????}????
????}
acquire()?是獲取獨(dú)占鎖的核心方法,咱先把整體邏輯講完,然后再對原碼進(jìn)行逐行分析。
tryAcquire(arg)
嘗試獲取鎖,成功返回true,否則返回false
addWaiter(Node.EXCLUSIVE)
自旋的方式入隊(duì),直到成功入隊(duì)為止,否則重試
acquireQueued(final Node node, int arg)
再次嘗試獲取鎖,若成功則返回,失敗了就阻塞線程。其中阻塞線程是調(diào)用了?LockSupport.park()?方法
四、ReentrantLock解鎖邏輯
????public?void?unlock()?{
????????sync.release(1);
????}
????public?final?boolean?release(int?arg)?{
????????if?(tryRelease(arg))?{
????????????Node?h?=?head;
????????????if?(h?!=?null?&&?h.waitStatus?!=?0)
????????????????unparkSuccessor(h);
????????????return?true;
????????}
????????return?false;
????}
tryRelease(arg)?
修改 state 屬性,state = 0 時(shí),即解鎖成功
unparkSuccessor(h)?
喚醒頭節(jié)點(diǎn),使其去搶鎖,其中解除阻塞,是調(diào)用了LockSupport.unpark()方法
用本文開頭的代碼,來個(gè)第一階段的總結(jié)
???????
???????lock.lock();?//?假設(shè)10個(gè)線程同時(shí)走到這行,只可能有一個(gè)線程拿到鎖,繼續(xù)往下走,其他線程阻塞,停在這一行。
???????for(int?j?=?0;?j?1000;?j++){
???????????total++;
???????}
???????lock.unlock();?//?執(zhí)有鎖的線程釋放了鎖,阻塞的線程有機(jī)會(huì)搶鎖,
???????????//?搶鎖成功的可以往下走,其余的繼續(xù)阻塞。
???????
在加鎖解鎖過程中,用了CAS,用了自旋,簡要說下它倆啥意思,然后再開始講原碼。
CAS?可以保證,不論并發(fā)有多高,只可能有一個(gè)線程執(zhí)行成功。
比如說,現(xiàn)在賬戶里有10塊,現(xiàn)在要給賬戶上加5塊錢,三個(gè)線程同時(shí)執(zhí)行這個(gè)操作。
CAS要求傳兩個(gè)值過去?原來的值10,改動(dòng)后的值15。底層執(zhí)行的時(shí)候,先比較一下,當(dāng)前值是不是10。
如果不是,就直接返回false,如果是10,那就將10改為15。
這里比較并修改是原子操作,底層語言保證這一點(diǎn)。
剛剛說的那三個(gè)線程,只有一個(gè)線程會(huì)修改賬戶的錢,并返回true,其它兩個(gè)會(huì)返回失敗,不修改賬戶的錢。
自旋?簡而言之就是死循環(huán),比如 while(true){},名字很高大上,其實(shí)就那么回事。
至此,ReentrantLock加鎖解鎖,最基本的知識(shí)講完了。若還不太理解,從頭現(xiàn)看,不要看下邊的。
如果說是初次接觸ReentrantLock,那就不要往下看了,那是會(huì)看吐的。聽話,別看!
歡迎轉(zhuǎn)載,碼字不易,請注明出處:https://editor.csdn.net/md/?articleId=108818343。
五、加鎖原碼分析
先看下,加鎖的流程圖,有個(gè)具體的印象,再看源碼

假設(shè)有ABCD四個(gè)線程,同時(shí)執(zhí)行到加鎖這行代碼。前文說過,state是0,代表可搶鎖。
????????final?void?lock()?{
????????????if?(compareAndSetState(0,?1))?//?假設(shè)A執(zhí)行此代碼成功
????????????????setExclusiveOwnerThread(Thread.currentThread());?//?標(biāo)識(shí)哪個(gè)線程執(zhí)有該鎖
????????????else
????????????????acquire(1);
????????}
//?這里compareAndSetState(0,?1),就是進(jìn)行CAS操作
????protected?final?boolean?compareAndSetState(int?expect,?int?update)?{
????????//?unsafe類調(diào)用的是native方法
????????return?unsafe.compareAndSwapInt(this,?stateOffset,?expect,?update);
????}
????
//?線程A拿到了鎖就直接返回了,那線程B?C?D?就進(jìn)入?acquire(1)?方法
????public?final?void?acquire(int?arg)?{
????????if?(!tryAcquire(arg)?&&?acquireQueued(addWaiter(Node.EXCLUSIVE),?arg)){
?????????????selfInterrupt();
????????}
????}
?先說?tryAcquire(arg)?就是搶鎖
??//?tryAcquire?這個(gè)方法,非公平鎖調(diào)用這個(gè)方法,傳入?yún)?shù)是1,這個(gè)與可重入相關(guān),待會(huì)再細(xì)說。
????????protected?final?boolean?tryAcquire(int?acquires)?{
????????????return?nonfairTryAcquire(acquires);
????????}
????????final?boolean?nonfairTryAcquire(int?acquires)?{
????????????final?Thread?current?=?Thread.currentThread();
????????????int?c?=?getState();?//?獲取?state狀態(tài),
????????????if?(c?==?0)?{?//?state是0,繼續(xù)搶鎖。剛進(jìn)入lock?沒搶到,現(xiàn)在可能鎖已釋放,有可能這次就搶成功了。
????????????????if?(compareAndSetState(0,?acquires))?{?//?B?C?D?線程再次競爭拿鎖
????????????????????setExclusiveOwnerThread(current);
????????????????????return?true;?//?拿到了鎖,lock?方法就結(jié)束了。
????????????????}
????????????}
????????????//?來搶鎖的線程,本身就執(zhí)有鎖,說明是再次加鎖,state?再加?1
????????????else?if?(current?==?getExclusiveOwnerThread())?{
????????????????int?nextc?=?c?+?acquires;
????????????????if?(nextc?0)?//?overflow?內(nèi)在溢出了,拋出異常
????????????????????throw?new?Error("Maximum?lock?count?exceeded");
????????????????setState(nextc);?
????????????????return?true;
????????????}
????????????return?false;?//?搶鎖失敗
????????}
????????
這個(gè)方法沒有難理解的邏輯,不過,setState(nextc); 這個(gè)方法沒有用 CAS,這里會(huì)不會(huì)是bug
明確的告訴你,不是,因?yàn)檫@里不存在并發(fā)
current?==?getExclusiveOwnerThread()
即當(dāng)前線程是執(zhí)有鎖的線程,只可能有一個(gè)線程進(jìn)入這人條件分支,不需要用CAS
這里就是ReentrantLock可重入的體現(xiàn),state=0,指鎖不屬于任何線程,
當(dāng)某線程首次搶到鎖,state=1,
此線程未釋放鎖的情況下,再次搶到鎖,state=2,
這種情況下,只有連續(xù)釋放兩次鎖,其它線程才可能搶到該鎖。
這就是?ReentrantLock?鎖的可重入。
?詳細(xì)說說?acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
addWaiter(Node.EXCLUSIVE),入隊(duì),自旋能保證,一定入隊(duì)成功
addWaiter(Node.EXCLUSIVE)??這個(gè)方法就是用自旋的方式,保證線程入隊(duì)
????private?Node?addWaiter(Node?mode)?{
????????Node?node?=?new?Node(Thread.currentThread(),?mode);?//?線程與Node綁定
????????Node?pred?=?tail;
????????if?(pred?!=?null)?{?//?隊(duì)列已經(jīng)初始化,直接將new?出來的node?放到隊(duì)尾
????????????node.prev?=?pred;
????????????if?(compareAndSetTail(pred,?node))?{?//?CAS?設(shè)置隊(duì)尾
????????????????pred.next?=?node;
????????????????return?node;
????????????}
????????}
????????//?走到這里,說明隊(duì)列未初始化,或者上面并發(fā)入隊(duì),入隊(duì)失敗了。
????????enq(node);?
????????return?node;
????}
?//?自旋方式入隊(duì)
????private?Node?enq(final?Node?node)?{
????????for?(;;)?{?//?死循環(huán),保證入隊(duì)一定成功
????????????Node?t?=?tail;
????????????if?(t?==?null)?{?//?隊(duì)尾是null,說明得初始化隊(duì)列
????????????????if?(compareAndSetHead(new?Node()))
????????????????????tail?=?head;
????????????}?else?{
????????????????node.prev?=?t;
????????????????if?(compareAndSetTail(t,?node))?{
????????????????????t.next?=?node;
????????????????????return?t;
????????????????}
????????????}
????????}
????}
還記得上文畫的那個(gè)AQS的圖么
還是剛剛的4個(gè)線程,假設(shè)此刻 A 還沒有釋放鎖,那state一定不等于0,exclusiveOwnerThread記錄的就是線程A,
當(dāng)B C D線程都進(jìn)入addWaiter方法時(shí),圖中 head==null , tail == null,
想入隊(duì)就要先初始化,即 t == null那個(gè)分支的代碼。

從原碼來看,隊(duì)列用了哨兵,初始化就是new一個(gè)node不與任何線程綁定,
如下圖所示,這個(gè)就是頭節(jié)點(diǎn),thread==null,之后入隊(duì)的,thread一定有值
若 B C D線程中 B線程進(jìn)入if (t == null) { } 這段代碼,就完成了初始化,
那 C D 線程進(jìn)入else分支,
它們倆個(gè),誰執(zhí)行compareAndSetTail()成功,誰就入隊(duì),
假設(shè)是C入隊(duì)成功(如下圖),那 B D 線程進(jìn)入下一輪循環(huán),

那 B D 線程進(jìn)入下一輪循環(huán),若存在并發(fā),失敗那個(gè)得再循環(huán)入隊(duì)一次,
若不存在并發(fā),就順次入隊(duì)。但最終大概都是這個(gè)樣子。

這個(gè)方法里的邏輯是巨復(fù)雜的,不太好理解。
????final?boolean?acquireQueued(final?Node?node,?int?arg)?{
????????boolean?failed?=?true;
????????try?{
????????????boolean?interrupted?=?false;
????????????for?(;;)?{
????????????????final?Node?p?=?node.predecessor();?//?獲取前驅(qū)節(jié)點(diǎn)
????????????????if?(p?==?head?&&?tryAcquire(arg))?{?//?是隊(duì)列的第二個(gè)節(jié)點(diǎn),并且搶鎖成功
????????????????????setHead(node);
????????????????????p.next?=?null;?//?從隊(duì)列中剔除,等待GC?回收
????????????????????failed?=?false;
????????????????????return?interrupted;?//?lock?方法結(jié)束
????????????????}
????????????????if?(shouldParkAfterFailedAcquire(p,?node)?&&?parkAndCheckInterrupt())?{
?????????????????????interrupted?=?true;?
????????????????}
????????????}
????????}?finally?{
????????????if?(failed)
????????????????cancelAcquire(node);
????????}
????}
?//?設(shè)置頭節(jié)點(diǎn)
????private?void?setHead(Node?node)?{
????????head?=?node;
????????node.thread?=?null;
????????node.prev?=?null;
????}
先解釋下這個(gè)條件?p == head && tryAcquire(arg)?前驅(qū)節(jié)點(diǎn)是head,且此節(jié)點(diǎn)搶鎖成功,
結(jié)合上面那個(gè)圖,B C D 三個(gè)節(jié)點(diǎn),只有 C 節(jié)點(diǎn)才可以 搶鎖,不要問為什么,這是規(guī)矩。

假設(shè)在 B C D 執(zhí)行acquireQueued方法時(shí),恰好A釋放了鎖,C 線程剛好搶到了鎖,隊(duì)列就變成上圖的樣子。
B?D?線程進(jìn)入shouldParkAfterFailedAcquire(p,?node)???parkAndCheckInterrupt()?這兩上方法。
原來代表C線程的node成為新的頭節(jié)點(diǎn)(搶鎖成功,就要出隊(duì))。
head?指向新的頭節(jié)點(diǎn),頭節(jié)點(diǎn)thread?設(shè)置為null,
新舊頭節(jié)點(diǎn)之間的指針去掉,舊的頭節(jié)點(diǎn)等待GC回收。
在講解shouldParkAfterFailedAcquire(p, node)?、?parkAndCheckInterrupt()?這兩個(gè)方法之前,
說點(diǎn)題外話。
B?C?D?三個(gè)節(jié)點(diǎn),只有?C?節(jié)點(diǎn)才可以?搶鎖,Why???大神Doug?Lea?就是這么寫的,你想咋地!
個(gè)人認(rèn)為,這樣設(shè)計(jì),代碼實(shí)現(xiàn)簡單,線程都已經(jīng)進(jìn)入等待隊(duì)列了,說明并發(fā)比較高,
搶鎖就派個(gè)代表出去就行了,別的繼續(xù)在隊(duì)列中等,出去搶鎖的線程多了,CPU有意見。
再說,去的再多,也是只能是一個(gè)搶到鎖。
派誰去,當(dāng)然是頭節(jié)點(diǎn),去掉代碼實(shí)現(xiàn)簡單易懂,去其中任意一個(gè),代碼更加復(fù)雜。
不是說非公平鎖么,那隊(duì)列里,排在前面的先出隊(duì)列去搶鎖,很公平啊,哪里來的非公平?
這是個(gè)好問題,公平與非公平鎖,不是在這里體現(xiàn)的。
不管公平鎖還是非公平鎖,隊(duì)列里,都是前面那個(gè)出隊(duì)搶鎖,沒區(qū)別,
公平與非公平,主要差別是tryAcquire()?這個(gè)方法上,
公平鎖,若隊(duì)列不為空,沒入隊(duì)的線程不得搶鎖,
非公平鎖,若隊(duì)列不為空,沒入隊(duì)的線程卻可以搶鎖,
這時(shí)后到的線程可能先搶到鎖,即不公平。
言歸正傳
?shouldParkAfterFailedAcquire(p, node)?判斷是否要阻塞線程
????private?static?boolean?shouldParkAfterFailedAcquire(Node?pred,?Node?node)?{
????????int?ws?=?pred.waitStatus;?//?前驅(qū)節(jié)點(diǎn)的waitStatus
????????if?(ws?==?Node.SIGNAL)?//?Node.SIGNAL?表示?-1
????????????return?true;
????????if?(ws?>?0)?{
????????????do?{
????????????????node.prev?=?pred?=?pred.prev;
????????????}?while?(pred.waitStatus?>?0);
????????????pred.next?=?node;
????????}?else?{
????????????compareAndSetWaitStatus(pred,?ws,?Node.SIGNAL);
????????}
????????return?false;
????}
這段代碼邏輯很復(fù)雜,一句話,前驅(qū)節(jié)點(diǎn) waitStatus是-1,就返回true.
不過,今天講 lock() unlock() ,waitStatus都是初始狀態(tài)0。
之后的博客中,還會(huì)再次講到這個(gè)方法。
結(jié)合上圖,以線程?B為例??waitStatus狀態(tài)都是?0,前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn),其waitStatus也是0
首次進(jìn)入shouldParkAfterFailedAcquire方法,
執(zhí)行compareAndSetWaitStatus(pred,?ws,?Node.SIGNAL)這段代碼之后,前驅(qū)節(jié)點(diǎn)waitStatus=-1,
之后走下一行?return?false,?compareAndSetWaitStatus?方法結(jié)束。
那在方法acquireQueued()?中??for?(;;)?{},第一次循環(huán)就結(jié)束了,?B?沒拿到鎖
for?(;;)?{}?第二次循環(huán)開始,再次搶鎖,
假設(shè)還沒拿到,會(huì)第二次進(jìn)入shouldParkAfterFailedAcquire()
此時(shí)B?線程的前驅(qū)節(jié)點(diǎn),waitStatus=-1?該方法返回?true;(解鎖時(shí)會(huì)講到這一點(diǎn))
那程序會(huì)走到?parkAndCheckInterrupt()這個(gè)方法里,阻塞線程
也就是說,經(jīng)過兩次循環(huán),才會(huì)去阻塞線程,每次循環(huán)都會(huì)去搶鎖的
Doug Lea?設(shè)計(jì)的真是好,發(fā)現(xiàn)這個(gè)線程需要阻塞,還要再給次搶鎖的機(jī)會(huì)。
萬一搶到了呢!真的是搶不到,再真正阻塞線程。
?parkAndCheckInterrupt()?阻塞線程
????private?final?boolean?parkAndCheckInterrupt()?{
????????LockSupport.park(this);?//?阻塞線程
????????return?Thread.interrupted();?//?清除線程中斷標(biāo)記
????}
題外話
?park()阻塞了線程,有兩種途徑可以喚醒該線程:1)被unpark();2)被interrupt()。
?
?Thread.interrupted()當(dāng)且僅當(dāng)?線程被阻斷時(shí)返回true,它還會(huì)清除當(dāng)前線程的中斷標(biāo)記位,
若要了解 unpark() interrupt() 喚醒有何不同,請參看我的另外一篇博客——park后是如何被喚醒的
至此,lock 調(diào)用的內(nèi)層代碼講完了,再回頭看下?lián)屾i的總邏輯
//?線程A拿到了鎖就直接返回了,那線程B?C?D?就進(jìn)入?acquire(1)?方法
????public?final?void?acquire(int?arg)?{
?????//?搶鎖,直到搶到為止,沒搶到就在acquireQueued一直自旋轉(zhuǎn)。
????????if?(!tryAcquire(arg)?&&?acquireQueued(addWaiter(Node.EXCLUSIVE),?arg)){
?????????????selfInterrupt();?//?如果線程被中斷過,給線程打個(gè)標(biāo)記
????????}
????}
????static?void?selfInterrupt()?{
????????Thread.currentThread().interrupt();
????}
上面這段代碼很有意思,作為題外話說說,當(dāng)然你可以不看,有點(diǎn)繞。最好別看,看了會(huì)看糊涂的。
想想這段代碼?Thread.currentThread().interrupt();?什么時(shí)候會(huì)執(zhí)行?
只有tryAcquire(arg)返回?false?且?acquireQueued()?返回?true?的時(shí)候才可以。
再看下,acquireQueued()什么時(shí)候才會(huì)返回?true?呢??咱再看下原碼
????final?boolean?acquireQueued(final?Node?node,?int?arg)?{
????????boolean?failed?=?true;
????????try?{
????????????boolean?interrupted?=?false;
????????????for?(;;)?{
????????????????final?Node?p?=?node.predecessor();
????????????????if?(p?==?head?&&?tryAcquire(arg))?{
????????????????????setHead(node);
????????????????????p.next?=?null;?//?help?GC
????????????????????failed?=?false;
????????????????????return?interrupted;
????????????????}
????????????????if?(shouldParkAfterFailedAcquire(p,?node)?&&
????????????????????parkAndCheckInterrupt())
????????????????????interrupted?=?true;
????????????}
????????}?finally?{
????????????if?(failed)
????????????????cancelAcquire(node);
????????}
????}
接著上文說,B線程?在for?(;;)中,第二次進(jìn)入shouldParkAfterFailedAcquire(),返回true,
進(jìn)入parkAndCheckInterrupt(),調(diào)用?LockSupport.park(this)后,
返回?return?Thread.interrupted();???
而?Thread.interrupted()?結(jié)果是false,因?yàn)槟壳?B線程?未調(diào)用過?interrupt(),繼續(xù)自旋,
若在自旋過程中,其它的代碼調(diào)用了interrupt(),那么?parkAndCheckInterrupt()會(huì)返回?true,
interrupted?=?true;?會(huì)被執(zhí)行。
那?B線程在自旋過程中?parkAndCheckInterrupt()?會(huì)清除掉中斷標(biāo)記,但?interrupted?=?true是不會(huì)。
最終在acquire()?方法里,會(huì)明確的知道拿到鎖的線程,曾經(jīng)是否被中斷過,若中斷過,會(huì)重新在線程上做標(biāo)記。
還有一點(diǎn)要說的
線程B?在自旋過程中,第二次?for?循環(huán),會(huì)調(diào)用LockSupport.park(this)方法,程序就暫停了,不會(huì)再占用CPU資源了。
當(dāng)被unpark()或interrupt()喚醒時(shí),會(huì)接著自旋,要么拿到了鎖,
要么重新調(diào)用LockSupport.park(this)方法,繼續(xù)等待。
具體是被unpark()喚醒的,還是被interrupt()喚醒的,程序上做了區(qū)分。
區(qū)分不區(qū)分,lock(),?unlock()?用不到,啥時(shí)候乃至,之后的博客會(huì)寫。
好了,加鎖的原碼,已講解完了。真的是很復(fù)雜,相對應(yīng)的,解鎖的原碼簡單多了。
六、解鎖原碼分析
先看圖,加鎖與解鎖畫在一起,直觀感受下
????public?void?unlock()?{
????????sync.release(1);
????}
????public?final?boolean?release(int?arg)?{
????????if?(tryRelease(arg))?{
????????????Node?h?=?head;
????????????if?(h?!=?null?&&?h.waitStatus?!=?0)
????????????????unparkSuccessor(h);
????????????return?true;
????????}
????????return?false;
????}
tryRelease(int releases)?是修改state值的
????????protected?final?boolean?tryRelease(int?releases)?{
????????????int?c?=?getState()?-?releases;
????????????if?(Thread.currentThread()?!=?getExclusiveOwnerThread())
????????????????throw?new?IllegalMonitorStateException();
????????????boolean?free?=?false;
????????????if?(c?==?0)?{
????????????????free?=?true;
????????????????setExclusiveOwnerThread(null);
????????????}
????????????setState(c);
????????????return?free;
????????}
這段代碼比較容易理解,也不存在并發(fā),
調(diào)用一次 unlock(),state 值減1,當(dāng)state = 0,即解鎖成功,清除線程。
也就是說,連續(xù)加鎖三次,即調(diào)用了三次lock() 方法,state=3,
解鎖也得是3次,否則鎖不會(huì)被釋放。
unparkSuccessor(Node node)?是喚醒節(jié)點(diǎn)的
?if?(h?!=?null?&&?h.waitStatus?!=?0)?//?h?==?null?無等待隊(duì)列,h.waitStatus?==?0,說明后面沒有阻塞的隊(duì)列,即不需要喚醒。
?????????????????unparkSuccessor(h);
???private?void?unparkSuccessor(Node?node)?{
????????int?ws?=?node.waitStatus;
????????if?(ws?0)
????????????compareAndSetWaitStatus(node,?ws,?0);?//?負(fù)值設(shè)置為0
????????Node?s?=?node.next;?//?找到有效的后繼節(jié)點(diǎn)
????????if?(s?==?null?||?s.waitStatus?>?0)?{
????????????s?=?null;
????????????for?(Node?t?=?tail;?t?!=?null?&&?t?!=?node;?t?=?t.prev)
????????????????if?(t.waitStatus?<=?0)
????????????????????s?=?t;
????????}
????????if?(s?!=?null)
????????????LockSupport.unpark(s.thread);?//?將后繼節(jié)點(diǎn)喚醒。
????}
?//?這段代碼其實(shí)很有意思的,說點(diǎn)題外話
?shouldParkAfterFailedAcquire()?這個(gè)方法還記得不,
?要阻塞節(jié)點(diǎn)的前提是,該節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)?waitStatus=?-1
?
?unparkSuccessor?這個(gè)方法中,s?是后繼節(jié)點(diǎn),若是null,說明不存在等待隊(duì)列,無需喚醒。
?for()?循環(huán)中代碼,可保證,s?是最前面的那個(gè)排除節(jié)點(diǎn),喚醒它搶鎖,因?yàn)殒i釋放了。
?
七、公平鎖的實(shí)現(xiàn)邏輯
前面介紹是以非公平鎖為例子來說明的,公平鎖實(shí)現(xiàn)與這類似。
先記住差別,前面也說過了,
公平鎖,等待線程不為空,只有入隊(duì)才可以搶鎖
非公平鎖,等待線程不為空,也可以搶鎖。
現(xiàn)在看原碼級(jí)別的實(shí)現(xiàn)
ReentrantLock?lock?=?new?ReentrantLock(true);?//?傳入?yún)?shù)?true?即是公平鎖
????????final?void?lock()?{?//?公平鎖lock方法,直接調(diào)用acquire(),?非公平鎖是先搶鎖,失敗用調(diào)用acquire()
????????????acquire(1);
????????}
公平鎖與非公平鎖,調(diào)用的 actuire() 方法是一樣的,不一樣的是tryAcquire()
????public?final?void?acquire(int?arg)?{
????????if?(!tryAcquire(arg)?&&
????????????acquireQueued(addWaiter(Node.EXCLUSIVE),?arg))
????????????selfInterrupt();
????}
??//?公平鎖tryAcquire()邏輯,與非公平鎖區(qū)別是多了!hasQueuedPredecessors()?這個(gè)判斷
????????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;
????????}
公平鎖與非公平鎖,差別就一行代碼?hasQueuedPredecessors()?,看下它的原碼
????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());
????}
這個(gè)方法很精煉,直接說邏輯,
若等待隊(duì)列為空,即未初始化,返回 false;
若等待隊(duì)列已初始化,哨兵結(jié)點(diǎn)沒有后繼結(jié)點(diǎn),返回false;
若哨兵結(jié)點(diǎn)有后繼結(jié)點(diǎn),后繼結(jié)點(diǎn)的線程是當(dāng)前線程,返回false;
其它情況返回 true
返回false就是可以搶鎖。
理解了這個(gè)之后,再看 tryAcquire()
????????????if?(c?==?0)?{?//?state?是?0?,可以搶鎖
????????????????if?(!hasQueuedPredecessors()?&&??//?等待隊(duì)列中有線程在等待時(shí),只有頭節(jié)點(diǎn)的后繼線程可搶鎖,其它沒資格。
????????????????????compareAndSetState(0,?acquires))?{
????????????????????setExclusiveOwnerThread(current);
????????????????????return?true;
????????????????}
????????????}
由此看出,tryAcquire() 方法保證了,搶鎖時(shí),若等待隊(duì)列中有線程在等待,外來的線程就不能搶鎖,只能先入隊(duì),即排在后面。
八、小結(jié)
歡迎轉(zhuǎn)載,碼字不易,請注明出處:https://editor.csdn.net/md/?articleId=108818343。
ReentrantLock 中 lock(), unlock() 方法的分析,至此結(jié)束,從中可以看出以下幾點(diǎn)
ReentrantLock 可實(shí)現(xiàn)公平鎖和非公平鎖,其差別是,等待隊(duì)列有線程等待時(shí),搶鎖邏輯不同
ReentrantLock 手動(dòng)加鎖,解鎖。加鎖解鎖次數(shù)必需相等,否則鎖不會(huì)被釋放
ReentrantLock 可實(shí)現(xiàn)鎖的重入,這與state數(shù)值有關(guān)
另外,ReentrantLock 鎖有可中斷的特性,本文沒有涉及到,隨后的博客會(huì)講解這個(gè)。
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循?CC 4.0 BY-SA?版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:
https://blog.csdn.net/every__day/article/details/108818343


??? ?
感謝點(diǎn)贊支持下哈?
