<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>

          JUC并發(fā)編程之ReentrantLock非公平鎖源碼詳解

          共 17827字,需瀏覽 36分鐘

           ·

          2021-07-03 16:43


          點(diǎn)擊上方藍(lán)字 關(guān)注我吧



          1
          前言

          大家伙好,已經(jīng)時(shí)隔將近一個(gè)月沒(méi)更新博客了,今天分享的內(nèi)容點(diǎn)為ReentrantLock非公平鎖源碼,為什么分享這個(gè)呢?因?yàn)樵诓l(fā)情況下,難免會(huì)造成數(shù)據(jù)安全性問(wèn)題,而ReentrantLock恰恰也是解決了并發(fā)情況下同時(shí)一起操作數(shù)據(jù)的安全性問(wèn)題,同樣該知識(shí)點(diǎn)也是基礎(chǔ)中的重點(diǎn)啦~


          2
          什么是ReentrantLock


          ReentLock是基于AQS框架進(jìn)行實(shí)現(xiàn),同樣也是解決多線程并發(fā)手段之一,它的功能類似于Synchronized是一種互斥鎖,可以保證線程安全。不同的在于它比Synchronized多了很多強(qiáng)大特性,例如支持公平鎖、共享鎖、手動(dòng)加鎖和釋放鎖以及允許中斷操作


          3
          AQS是什么


          AQS它的全稱是 "AbstractQueuedSynchronizer" 它采用了模板方法設(shè)計(jì)模式在內(nèi)部定義獲取和釋放同步狀態(tài)的模板方法,并留下鉤子函數(shù)提供給子類進(jìn)行擴(kuò)展,由子類來(lái)具體控制獲取與釋放鎖的細(xì)節(jié)點(diǎn),從而滿足自身特性的需求。除此之外AQS中還提供好了線程阻塞與喚醒的方法,以及在AQS中還有一個(gè)特別重要的細(xì)節(jié)點(diǎn),就是 "雙向鏈表",當(dāng)多個(gè)線程去同時(shí)去搶占鎖的時(shí)候,會(huì)就未搶到鎖的節(jié)點(diǎn),封裝成Node節(jié)點(diǎn),進(jìn)行等待下一波喚醒獲取鎖。


          下圖就是基于AQS所實(shí)現(xiàn)的一些比較強(qiáng)大的功能類,例如公平與非公平鎖,信號(hào)量,線程池


          在AQS抽象類中,有幾個(gè)特別重要的屬性,首先說(shuō)下AQS父類AbstractOwnableSynchronizer的成員屬性
          // 標(biāo)識(shí)拿到鎖的是哪個(gè)線程private transient Thread exclusiveOwnerThread;


          接著說(shuō)下AQS本身成員屬性
          // 標(biāo)識(shí)頭節(jié)點(diǎn)private transient volatile Node head;// 標(biāo)識(shí)尾節(jié)點(diǎn)private transient volatile Node tail;// 同步狀態(tài),為0時(shí),說(shuō)明可以搶鎖private volatile int state;


          最后是AQS內(nèi)部類Node的幾個(gè)重要屬性
          // 記錄Node狀態(tài)volatile int waitStatus;// 標(biāo)識(shí)Node是哪個(gè)線程所持有volatile Node prev;// 前驅(qū)節(jié)點(diǎn)(當(dāng)前Node的上一個(gè)是誰(shuí))volatile Node next;// 后繼節(jié)點(diǎn)(當(dāng)前Node的個(gè)一個(gè)是誰(shuí))volatile Thread thread;



          4
          ReentrantLock源碼詳細(xì)分析


          如何使用ReentLock


          下面是一個(gè)簡(jiǎn)單的lock案例,使用lock需要注意點(diǎn),我們獲取鎖需要被try包裹,釋放鎖一定要在finally里面,為什么需要這么做呢?是因?yàn)槿绻鄠€(gè)線程同時(shí)進(jìn)來(lái)?yè)屾i,當(dāng)其中一個(gè)線程搶到鎖后,執(zhí)行完業(yè)務(wù)邏輯還沒(méi)來(lái)得及釋放鎖就發(fā)生了異常,就會(huì)導(dǎo)致鎖沒(méi)有被釋放,從而該業(yè)務(wù)邏輯一直被卡住。所以為了避免這一現(xiàn)象的發(fā)生,我們的釋放鎖一定要寫(xiě)在finally里面啦~

          public class ReentrantLockTest {    // 初始化Lock類    static final Lock lock = new ReentrantLock();    static int sum = 0;    public static void main(String[] args) throws InterruptedException {        for (int i = 0; i < 3; i++) {            Thread thread = new Thread(new Runnable() {                @Override                public void run() {                    try {                        // 獲取鎖                        lock.lock();                        for (int i1 = 0; i1 < 10000; i1++) {                            sum++;                        }                    } catch (Exception e) {                        e.printStackTrace();                    } finally {                        //釋放鎖                        lock.unlock();                    }                }            });            thread.start();        }        TimeUnit.SECONDS.sleep(1);        System.out.println(sum);    }}


          源碼分析
          了解完如何使用lock鎖后,開(kāi)始進(jìn)入我們的正式源碼分析流程


          在ReentLock里面,其中有兩個(gè)構(gòu)造方法值得我們注意

          第一個(gè)是無(wú)參構(gòu)造方法,它是非公平鎖的一種實(shí)現(xiàn)方式,也是我們使用Lock鎖默認(rèn)的一種方式
          第二個(gè)是有參構(gòu)造方法,根據(jù)傳入的布爾值,決定是否為公平鎖還是非公平鎖。


          該文章所講的是非公平鎖,所以這里只放非公平鎖的類結(jié)構(gòu)圖


          非公平鎖加鎖流程
          加鎖流程從 “ lock.lock(); ” 開(kāi)始
          public void lock() {    sync.lock();}


          加鎖流程真正意義上的入口

          首先嘗試以最快的方式獲取鎖,當(dāng)多個(gè)線程同時(shí)進(jìn)來(lái)了,只會(huì)有其中一個(gè)線程以CAS的方式將state的值更新為1,前提條件是只有當(dāng)state的值為0的時(shí)候才能更新成功,同時(shí)會(huì)記錄獲取鎖的線程,若獲取鎖失敗,則執(zhí)行acquire方法

          static final class NonfairSync extends Sync {    private static final long serialVersionUID = 7316153563782823691L;    /**     * Performs lock.  Try immediate barge, backing up to normal     * acquire on failure.     */    final void lock() {        //以cas的方式將AQS中的state屬性值從0更新為1        if (compareAndSetState(0, 1))            //如果更新成功,則將當(dāng)前線程設(shè)置為持有鎖的線程            setExclusiveOwnerThread(Thread.currentThread());        else            //獲取鎖失敗,則進(jìn)入該方法            acquire(1);    }    protected final boolean tryAcquire(int acquires) {        return nonfairTryAcquire(acquires);    }}


          來(lái)看看CAS是如何設(shè)置值的,它里面有四個(gè)參數(shù),第一個(gè)參數(shù):需要修改的對(duì)象,第二個(gè)參數(shù):value變量的內(nèi)存偏移地址,第三個(gè)參數(shù):期望內(nèi)存值,第四個(gè)參數(shù):需要修改的值
          流程概述: 首先AQS中默認(rèn)的state值為0,當(dāng)一個(gè)線程通過(guò)CAS設(shè)置值,會(huì)先找到AQS的對(duì)象,然后通過(guò)state的偏移量獲取內(nèi)存中的值,接著與我們傳進(jìn)來(lái)的期望內(nèi)存值會(huì)和內(nèi)存地址的值進(jìn)行比較,如果一致話,則進(jìn)行修改成功,否則修改失敗。
          protected final boolean compareAndSetState(int expect, int update) {    // See below for intrinsics setup to support this    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}


          如果線程通過(guò)cas設(shè)置值失敗(獲取鎖失敗),則進(jìn)入acquire方法,該方法的主要邏輯都在if判斷中,里面有幾個(gè)重要的方法,tryAcquire()、acquireQueued()、addWaiter(),加鎖邏輯幾乎都封裝在這三個(gè)方法中,清楚了三個(gè)方法的邏輯,加鎖的流程也差不多該清楚了哈
          public final void acquire(int arg) {    if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))        selfInterrupt();}


          通用獲取鎖tryAcquire方法
          為什么說(shuō)是通用獲取方法呢?因?yàn)樵贏QS中該方法是一個(gè)鉤子方法,也就是我們前面所說(shuō)的該方法是個(gè)模板方法,具體的加鎖邏輯由子類自身的特性去具體實(shí)現(xiàn)的


          在ReentrantLock中,它的加鎖鉤子方法如下所示,如果不進(jìn)行重寫(xiě)該方法,則強(qiáng)制拋出異常。

          protected boolean tryAcquire(int arg) {    throw new UnsupportedOperationException();}


          找到關(guān)鍵點(diǎn) -> 非公平類加鎖入口


          如下則是非公平鎖的實(shí)現(xiàn)方式,在其底層調(diào)用了nonfairTryAcquire()方法

          protected final boolean tryAcquire(int acquires) {    return nonfairTryAcquire(acquires);}


          進(jìn)入nonfairTryAcquire()方法后,內(nèi)部代碼精髓在于ReentrantLock的重入鎖概念就是在該方法內(nèi)實(shí)現(xiàn)的 ,用白話文解釋重入鎖的概念,在要進(jìn)入家門(mén)前,需要通過(guò)人臉識(shí)別打開(kāi)外門(mén),識(shí)別成功后進(jìn)入到外門(mén)內(nèi),但是家里還有內(nèi)門(mén)需要打開(kāi),同樣需要通過(guò)人眼識(shí)別打開(kāi)內(nèi)門(mén),才能成功進(jìn)入到自己溫馨家里。不知道這段話是否理解,簡(jiǎn)單理解,兩扇門(mén)的打開(kāi)前提,必須是同一個(gè)人才能進(jìn)入,否則將會(huì)被拒絕在外。
          那么通過(guò)程序來(lái)理解的話,"Thread-1"線程在前面通過(guò)lock.lock()獲取了鎖,然后該線程在調(diào)用了其他方法內(nèi)部又有一個(gè)lock.lock()方法,此時(shí)就有了嵌套鎖,在程序里面,假如是同一個(gè)線程獲取了兩次鎖,那么就會(huì)將state值進(jìn)行自增+1,來(lái)代表該線程獲取了幾次鎖。如果獲取鎖的線程與持有鎖的線程不一致,那么就重入鎖失敗。
          final boolean nonfairTryAcquire(int acquires) {    //記錄當(dāng)前線程    final Thread current = Thread.currentThread();    //獲取當(dāng)前state的值    int c = getState();    //如果state的值為0,則證明沒(méi)有線程搶占鎖    if (c == 0) {        // 通過(guò)cas進(jìn)行加鎖操作        if (compareAndSetState(0, acquires)) {            // 記錄加鎖線程            setExclusiveOwnerThread(current);            return true;        }    }    // 如果鎖已經(jīng)被搶占,且當(dāng)前線程就是持有鎖的線程,則說(shuō)明該鎖被重入    else if (current == getExclusiveOwnerThread()) {        //計(jì)算要更新后的state值        int nextc = c + acquires;        if (nextc < 0) // overflow            throw new Error("Maximum lock count exceeded");        //給state設(shè)置值,該方式為非同步        setState(nextc);        //重入鎖獲取成功        return true;    }    //獲取鎖失敗    return false;}


          退回到上層的acquire()方法
          如果tryAcquire()返回false,則說(shuō)明線程獲取鎖失敗,然后取反操作值為true,那么就可以進(jìn)入下一個(gè)流程,然后執(zhí)行addWaiter(Node.EXCLUSIVE), arg)方法,該方法是構(gòu)建雙向鏈表的關(guān)鍵點(diǎn),同樣也展現(xiàn)出線程獲取鎖失敗后如何安全的加入到同步等待隊(duì)列中的。先提前說(shuō)明一下Node.EXCLUSIVE的值是為NULL, 讓我們一起來(lái)看看該方法
          public final void acquire(int arg) {    if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))        selfInterrupt();}


          線程獲取鎖失敗,加入到同步隊(duì)列
          第一步驟:將當(dāng)前線程封裝為一個(gè)Node節(jié)點(diǎn)
          第二步驟:獲取鏈表末尾節(jié)點(diǎn),如果當(dāng)前AQS還沒(méi)有生成雙向鏈表的話,那么就會(huì)通過(guò)enq進(jìn)行構(gòu)建雙向鏈表
          private Node addWaiter(Node mode) {    //創(chuàng)建一個(gè)節(jié)點(diǎn),將線程實(shí)例封裝到Node節(jié)點(diǎn)內(nèi)部,當(dāng)前mode這里是為null的    Node node = new Node(Thread.currentThread(), mode);    // Try the fast path of enq; backup to full enq on failure     //獲取末尾節(jié)點(diǎn)    Node pred = tail;    //如果末尾節(jié)點(diǎn)不為空,則證明前面已經(jīng)有線程排隊(duì)啦    if (pred != null) {        //將末尾節(jié)點(diǎn)的引用交給當(dāng)前線程的前節(jié)點(diǎn)        node.prev = pred;        // 通過(guò)cas,將當(dāng)前線程設(shè)置為鏈表的尾節(jié)點(diǎn)        if (compareAndSetTail(pred, node)) {            // 將前尾節(jié)點(diǎn)的next引用,指向當(dāng)前節(jié)點(diǎn),那么當(dāng)前節(jié)點(diǎn)就是鏈表尾節(jié)點(diǎn)了            pred.next = node;            return node;        }    }    //構(gòu)建鏈表核心方法    enq(node);    return node;}


          進(jìn)入到enq方法,來(lái)看看它是如何進(jìn)行構(gòu)建雙向鏈表的。
          當(dāng)線程第一次進(jìn)來(lái),拿到當(dāng)前鏈表尾節(jié)點(diǎn),如果尾節(jié)點(diǎn)為null的話,說(shuō)明當(dāng)前鏈表還沒(méi)有構(gòu)成,則通過(guò)cas添加一個(gè)空的頭節(jié)點(diǎn),然后再將頭節(jié)點(diǎn)的引用交給尾節(jié)點(diǎn),那么此時(shí)意味著頭尾是處于在同一個(gè)內(nèi)存地址中,此時(shí)可能有小伙伴會(huì)有疑惑,當(dāng)其中一個(gè)節(jié)點(diǎn)發(fā)生變化,那么另外一個(gè)節(jié)點(diǎn)也會(huì)發(fā)生變化,豈不是會(huì)發(fā)生很嚴(yán)重的后果?看到這先別著急。我們接著往下。
          我們看到代碼,當(dāng)?shù)谝淮窝h(huán)如果沒(méi)有尾節(jié)點(diǎn)就構(gòu)造一個(gè)頭尾節(jié)點(diǎn)出來(lái),然后又繼續(xù)執(zhí)行第二次循環(huán),第二次循環(huán)的時(shí)候則會(huì)將當(dāng)前線程的prev引用指向?yàn)槌跏蓟补?jié)點(diǎn),接著又通過(guò)CAS將當(dāng)前線程設(shè)置為尾節(jié)點(diǎn),然后將初始化尾節(jié)點(diǎn)的next引用指向?yàn)楫?dāng)前線程節(jié)點(diǎn),這樣是不是雙向鏈表就構(gòu)成了呢
          private Node enq(final Node node) {        //自旋        for (;;) {            //獲取當(dāng)前雙向鏈表的末尾節(jié)點(diǎn)            Node t = tail;            //如果當(dāng)前還沒(méi)有雙向鏈表,則進(jìn)行構(gòu)建鏈表            if (t == null) { // Must initialize                //初始化一個(gè)head節(jié)點(diǎn),空的node節(jié)點(diǎn)                if (compareAndSetHead(new Node()))                    //將head節(jié)點(diǎn)賦值給tail節(jié)點(diǎn),這樣的頭尾節(jié)點(diǎn)都已生成好了                    tail = head;            } else {                //自旋第二次會(huì)進(jìn)來(lái)                //頭節(jié)點(diǎn)設(shè)置為 空節(jié)點(diǎn)                node.prev = t;                //通過(guò)cas,將當(dāng)前節(jié)點(diǎn)設(shè)置為末尾節(jié)點(diǎn)                if (compareAndSetTail(t, node)) {                    //設(shè)置尾節(jié)點(diǎn)引用                    t.next = node;                    return t;                }            }        }    }


          方便大家理解,我放上一張圖


          那么到此獲取鎖失敗的線程,通過(guò)構(gòu)建鏈表,然后假如到同步隊(duì)列的邏輯到此就結(jié)束了,但是線程加入同步隊(duì)列后會(huì)做什么我們并不清楚,這部分我們并不清楚,所以我們接著退回到acquire方法來(lái),我們進(jìn)入到acquireQueued方法內(nèi)部,看看它里面做了些啥邏輯

          public final void acquire(int arg) {    if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))        selfInterrupt();}


          線程加入到同步隊(duì)列后會(huì)做什么呢
          acquireQueued方法的邏輯主要都在for循環(huán)里面,這是一個(gè)死循環(huán),主要由兩個(gè)if判斷構(gòu)成。


          第一個(gè)if判斷中,首先會(huì)判斷當(dāng)前線程的前驅(qū)節(jié)點(diǎn)是否為頭節(jié)點(diǎn),如果是則嘗試獲取鎖,獲取鎖成功則將當(dāng)前線程節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),為什么必須是前驅(qū)節(jié)點(diǎn)才嘗試去獲取鎖,因?yàn)檎G闆r下,頭節(jié)點(diǎn)才是持有鎖的線程,頭節(jié)點(diǎn)線程釋放掉鎖后,會(huì)喚醒后驅(qū)節(jié)點(diǎn)的線程,這個(gè)時(shí)候被喚醒后才會(huì)進(jìn)行獲取鎖。舉個(gè)很形象的例子,就比如火車站窗口排隊(duì)買(mǎi)火車票,此時(shí)有人正在購(gòu)買(mǎi)火車票,而排在第二個(gè)人可以知道第一個(gè)人是否購(gòu)買(mǎi)完成,如果買(mǎi)完了我就可以去買(mǎi)票了啊,如果沒(méi)買(mǎi)完,那么第二個(gè)人后面的人就只能老老實(shí)實(shí)的進(jìn)行排隊(duì)購(gòu)票啊。


          那么第二個(gè)if判斷就是如果前面有人在排隊(duì)購(gòu)票,那么我就只能老老實(shí)實(shí)排在后面進(jìn)行等待唄。再舉個(gè)很形象的例子哈,有一款很好玩的游戲,因?yàn)檫@款游戲還沒(méi)發(fā)售需要進(jìn)行預(yù)約,此時(shí)我并不知道它什么時(shí)候才會(huì)發(fā)售,所以就先預(yù)約排隊(duì)嘛,當(dāng)要發(fā)售了,就會(huì)有工作人員去通知你,你預(yù)約的游戲可以已經(jīng)發(fā)售了,你可以進(jìn)行購(gòu)買(mǎi)了,這就是排隊(duì)與通知的。

           //Node: 為線程1的節(jié)點(diǎn)   arg:1final boolean acquireQueued(final Node node, int arg) {    boolean failed = true;    try {        boolean interrupted = false;        //只有獲取到鎖的線程才會(huì)跳出循環(huán)        for (;;) {            //獲取當(dāng)前線程節(jié)點(diǎn)的 prev 節(jié)點(diǎn)            final Node p = node.predecessor();             // 第二個(gè)線程 第一個(gè)條件為true , 第二個(gè)條件為false,因?yàn)榈诙€(gè)條件是 搶鎖邏輯            if (p == head && tryAcquire(arg)) {                setHead(node); //將當(dāng)前節(jié)點(diǎn)更新為頭節(jié)點(diǎn)                p.next = null; // help GC                failed = false;                return interrupted; //正常情況下死循環(huán)的唯一出口            }             // 將上一個(gè)節(jié)點(diǎn)設(shè)置為獨(dú)占鎖-1,條件返回fasle,第二次自旋進(jìn)來(lái)            if (shouldParkAfterFailedAcquire(p, node) && //判斷線程是否需要被阻塞                //阻塞當(dāng)前線程                parkAndCheckInterrupt())                interrupted = true;        }    } finally {        if (failed)            cancelAcquire(node);    }}


          我們來(lái)看看shouldParkAfterFailedAcquire方法是如何進(jìn)行判斷當(dāng)前線程是需要進(jìn)行排隊(duì)的。


          我們可以前驅(qū)節(jié)點(diǎn)pred的狀態(tài)會(huì)進(jìn)行不同處理

          1.pred狀態(tài)為SIGNAL,則返回true,表示要阻塞當(dāng)前線程
          2.pred狀態(tài)為CANCELLED,則一直往隊(duì)列頭部回溯直到找到一個(gè)狀態(tài)不為CANCELLED的結(jié)點(diǎn),將當(dāng)前節(jié)點(diǎn)node掛在這個(gè)結(jié)點(diǎn)的后面
          3.pred的狀態(tài)為初始化狀態(tài),此時(shí)通過(guò)compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法將pred的狀態(tài)改為SIGNAL。


          其實(shí)這個(gè)方法主要作用就是確保當(dāng)前結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)的狀態(tài)為SIGNAL,SIGNAL意味著線程釋放鎖后會(huì)喚醒后面阻塞的線程。畢竟只有確保能夠被喚醒,當(dāng)前線程才能放心的阻塞。

          private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {    int ws = pred.waitStatus;    if (ws == Node.SIGNAL) //狀態(tài)為SIGNAL        /*         * This node has already set status asking a release         * to signal it, so it can safely park.         */        return true;    if (ws > 0) {   //狀態(tài)為CANCELLED        /*         * Predecessor was cancelled. Skip over predecessors and         * indicate retry.         */        do {            node.prev = pred = pred.prev;        } while (pred.waitStatus > 0);        pred.next = node;    } else {   //狀態(tài)為初始化狀態(tài)(ReentrentLock語(yǔ)境下)        /*         * waitStatus must be 0 or PROPAGATE.  Indicate that we         * need a signal, but don't park yet.  Caller will need to         * retry to make sure it cannot acquire before parking.         */        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);    }    return false;}


          最后我們來(lái)看看parkAndCheckInterrupt()阻塞方法
          如果shouldParkAfterFailedAcquire返回true,則表示應(yīng)該阻塞當(dāng)前線程,執(zhí)行parkAndCheckInterrupt方法,這個(gè)方法比較簡(jiǎn)單,底層調(diào)用了LockSupport來(lái)阻塞當(dāng)前線程
          private final boolean parkAndCheckInterrupt() {    LockSupport.park(this);    return Thread.interrupted();}


          非公平鎖釋放流程
          解鎖入口
          釋放鎖的源碼比加鎖相對(duì)容易很多,我們來(lái)看看它的入口
          public void unlock() {    sync.release(1);}


          我們來(lái)看到release方法,該方法內(nèi)部有兩個(gè)if判斷,我門(mén)先進(jìn)入看看tryRelease做了些什么事情
          public final boolean release(int arg) {    if (tryRelease(arg)) {        Node h = head;        if (h != null && h.waitStatus != 0)            unparkSuccessor(h);        return true;    }    return false;}


          首先獲取當(dāng)前state的狀態(tài)值,如果當(dāng)前釋放鎖的線程與持有鎖的線程不一致,則拋出異常,如果一致則會(huì)判斷state是否為0,如果不為0則不會(huì)進(jìn)行是否鎖,因?yàn)榭赡転橹厝腈i,需要被釋放多次,如果state為0,則將當(dāng)前持有鎖的線程設(shè)置為null,然后再將state設(shè)回給主內(nèi)存中
          protected final boolean tryRelease(int releases) {    int c = getState() - releases; //計(jì)算待更新的state值    if (Thread.currentThread() != getExclusiveOwnerThread())        throw new IllegalMonitorStateException();    boolean free = false;    if (c == 0) {  //待更新的state值為0,說(shuō)明持有鎖的線程未重入,一旦釋放鎖其他線程將能獲取        free = true;        setExclusiveOwnerThread(null);  //清除鎖的持有線程標(biāo)記    }    setState(c);  //更新state值    return free;}


          接著我們?cè)谕嘶氐缴弦徊絩elease方法,當(dāng)我們執(zhí)行完tryRelease方法,如果返回true,則獲取當(dāng)前鏈表的頭節(jié)點(diǎn),接著有兩個(gè)條件判斷:
          h != null :為了防止鏈表為空,就是沒(méi)有任何線程處于在隊(duì)列中,則沒(méi)有線程可喚醒
          h.waitStatus != 0:防止隊(duì)列中雖說(shuō)有線程存在,但是該線程還未被阻塞住,前面就有說(shuō)到,線程在阻塞之前會(huì)將自己的前一個(gè)節(jié)點(diǎn)的狀態(tài)設(shè)置為-1,否則不會(huì)進(jìn)行阻塞自己
          當(dāng)兩個(gè)條件都成立,則進(jìn)入unparkSuccessor(),喚醒后驅(qū)節(jié)點(diǎn)線程
          public final boolean release(int arg) {    if (tryRelease(arg)) {        Node h = head;        if (h != null && h.waitStatus != 0)            unparkSuccessor(h);        return true;    }    return false;}


          然后我們來(lái)看看最終該方法它是如何喚醒后驅(qū)節(jié)點(diǎn)的。
          private void unparkSuccessor(Node node) {    // 先獲取head節(jié)點(diǎn)的狀態(tài),應(yīng)該是等于-1,原因在shouldParkAfterFailedAcquire方法中有體現(xiàn)    int ws = node.waitStatus;     // 由于-1會(huì)小于0,所以更新改為0    if (ws < 0)        compareAndSetWaitStatus(node, ws, 0);    // 獲取第一個(gè)正常排隊(duì)的節(jié)點(diǎn)    Node s = node.next;     //正常解鎖流程不會(huì)走該if判斷    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;    }     // 正常來(lái)說(shuō)第一個(gè)排隊(duì)的節(jié)點(diǎn)不應(yīng)該為空,所以直接把第一個(gè)排隊(duì)的線程喚醒    if (s != null)        LockSupport.unpark(s.thread);}


          如果線程被喚醒了,則會(huì)接著這個(gè)方法繼續(xù)執(zhí)行下去
          private final boolean parkAndCheckInterrupt() {    LockSupport.park(this);    return Thread.interrupted();}


          那么到此ReentranLock非公平鎖的加鎖與釋放鎖的流程,到此就結(jié)束啦

          ReentrantLock視頻分享,結(jié)合博文觀看效果更佳~


          我是黎明大大,我知道我沒(méi)有驚世的才華,也沒(méi)有超于凡人的能力,但畢竟我還有一個(gè)不屈服,敢于選擇向命運(yùn)沖鋒的靈魂,和一個(gè)就是傷痕累累也要義無(wú)反顧走下去的心。


          如果您覺(jué)得本文對(duì)您有幫助,還請(qǐng)關(guān)注點(diǎn)贊一波,后期將不間斷更新更多技術(shù)文章


          掃描二維碼關(guān)注我
          不定期更新技術(shù)文章哦



          JUC并發(fā)編程之Synchronized關(guān)鍵字詳解

          JUC并發(fā)編程之MESI緩存一致協(xié)議詳解

          JUC并發(fā)編程之Volatile關(guān)鍵字詳解

          JUC并發(fā)編程之JMM內(nèi)存模型詳解

          深入Hotspot源碼與Linux內(nèi)核理解NIO與Epoll



          發(fā)現(xiàn)“在看”和“贊”了嗎,因?yàn)槟愕狞c(diǎn)贊,讓我元?dú)鉂M滿哦
          瀏覽 44
          點(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>
                  91干比| 亲子乱伦视频 | xxx一区二区 | 先锋影音成人av 翔田千里无码破解 | 操逼黄色视频网站 |