<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          徹底弄懂ReentrantLock —— 超詳細(xì)的原碼分析

          共 900字,需瀏覽 2分鐘

           ·

          2020-10-13 05:35

          點(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?????????????new?Thread(()?->?{
          ????????????????try?{
          ????????????????????downLatch.await();
          ????????????????????for(int?j?=?0;?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?????????????new?Thread(()?->?{
          ????????????????try?{
          ????????????????????downLatch.await();
          ????????????????????lock.lock();?//?加鎖
          ????????????????????for(int?j?=?0;?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????????????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?????????????????????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?????????????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?????????????????????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)贊支持下哈?

          瀏覽 72
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  夜夜干视频| 靠逼网站| 国产精品久久久久久久9999 | 亚洲第一狼人社区 | 99精品人妻无码一区二区三区 |