最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

ReentrantrantLock底層實(shí)現(xiàn)原理及分析

 更新時(shí)間:2026年03月14日 09:32:35   作者:快樂的小三菊  
本文詳細(xì)介紹了Java并發(fā)包中ReentrantLock的實(shí)現(xiàn)原理,包括非公平鎖和公平鎖的加鎖和解鎖機(jī)制,以及可重入和可打斷特性,同時(shí),還介紹了條件變量的實(shí)現(xiàn)原理,通過await和signal方法來管理線程間的等待和喚醒

一、類圖

ReentrantrantLock 的類圖如下所示,可以看到,它也是實(shí)現(xiàn)了 Lock 接口,和我們自定義不可重入鎖的步驟是一樣的,其內(nèi)部也是維護(hù)了一個(gè)內(nèi)部類同步器類 Sync,這個(gè)同步器類也是繼承自 AQS,不過需要注意的是這里面的同步器類 Sync 是抽象的,有兩個(gè)實(shí)現(xiàn),一個(gè)是非公平鎖實(shí)現(xiàn) NonfairSync,一個(gè)是公平鎖實(shí)現(xiàn) FairSync。

二、非公平鎖實(shí)現(xiàn)原理

2.1 加鎖流程

先從構(gòu)造器開始看,默認(rèn)為非公平鎖實(shí)現(xiàn),如下代碼,其中 NonfairSync 繼承自 AQS

    public ReentrantLock() {
        sync = new NonfairSync();
    }

先看加鎖流程,如下的 lock() 方法,可以看到,方法內(nèi)部調(diào)用的是NonfairSync 實(shí)現(xiàn)的 lock() 方法

    public void lock() {
        sync.lock();
    }

接下來我們看下NonfairSync lock() 方法是如何實(shí)現(xiàn)的,如下圖:

當(dāng)沒有競(jìng)爭(zhēng)的時(shí)候,通過 CAS state 0 改成 1,并把 owner 線程改成自己,如下圖:

當(dāng)?shù)谝粋€(gè)競(jìng)爭(zhēng)出現(xiàn)時(shí),如下圖,此時(shí)owner 已經(jīng)是 Thread0 了,此時(shí)來了一個(gè) Thread-1,此時(shí) Thread-1 還是執(zhí)行 lock() 方法。

此時(shí) Thread-1 CAS 肯定是失敗的,此時(shí)就進(jìn)入 acquire() 方法,此方法的內(nèi)容如下所示:

1、進(jìn)入 tryAcquire 邏輯,這時(shí) state 已經(jīng)是 1,結(jié)果仍然失敗。

2、接下來進(jìn)入 addWaiter 邏輯,構(gòu)造 Node 隊(duì)列,如下圖,會(huì)在 head 后面添加一個(gè)節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)是雙向鏈表,即 head 指向一個(gè) node 節(jié)點(diǎn),node 節(jié)點(diǎn)又指向下一個(gè) node 節(jié)點(diǎn)。

圖中黃色三角表示該 Node waitStatus 狀態(tài),其中 0 為默認(rèn)正常狀態(tài),Node 的創(chuàng)建是懶惰的。

需要注意的是,首次創(chuàng)建 node 時(shí)會(huì)創(chuàng)建兩個(gè),其中第一個(gè) Node 稱為 Dummy(啞元)或哨兵,用來占位,并不關(guān)聯(lián)線程。只有第二個(gè) Node 才會(huì)關(guān)聯(lián)線程 Thread-1,因?yàn)樗?jìng)爭(zhēng)失敗了,因?yàn)樗堑谝粋€(gè)加入的,所以它指向了鏈表的尾部。其實(shí)這個(gè)就是我們的等待隊(duì)列。

等到創(chuàng)建成功之后,當(dāng)前線程就會(huì)進(jìn)入到acquireQueued 邏輯,如下圖,這個(gè)方法主要是在一個(gè)死循環(huán)中不斷嘗試獲得鎖,失敗后進(jìn)入 park 阻塞。

1、首先獲取前驅(qū)節(jié)點(diǎn),然后檢查前驅(qū)節(jié)點(diǎn),判斷前驅(qū)階段是否為頭節(jié)點(diǎn),如果是,說明 Thread-1 的這個(gè) Node 是處于第二位的,那么它就有資格再次調(diào)用 tryAcquire() 方法再次獲取鎖。

2、如果還是沒有獲取到鎖,此時(shí)就會(huì)進(jìn)入到shouldParkAfterFailedAcquire 邏輯,這個(gè)方法會(huì)把當(dāng)前線程節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的 waitStatus 改為 -1,改為 -1 的意思是它有責(zé)任喚醒它的后繼節(jié)點(diǎn),此時(shí)方法返回 false,如下圖:


3、shouldParkAfterFailedAcquire 執(zhí)行完畢回到 acquireQueued ,再次 tryAcquire 嘗試獲取鎖,當(dāng)然這時(shí)state 仍為 1,失敗。

4、當(dāng)再次進(jìn)入 shouldParkAfterFailedAcquire 時(shí),這時(shí)因?yàn)槠淝膀?qū) node waitStatus 已經(jīng)是 -1,這次返回 true

5、此時(shí)就會(huì)進(jìn)入 parkAndCheckInterrupt(),即 Thread-1 被阻塞住,用灰顏色表示,如下圖:

再次有多個(gè)線程經(jīng)歷上述過程競(jìng)爭(zhēng)失敗,就會(huì)變成如下的樣子:

2.2 解鎖流程

解鎖流程,如下的 lock() 方法,可以看到,方法內(nèi)部調(diào)用的同步器 AQS release() 方法。

    public void unlock() {
        sync.release(1);
    }

release()方法的內(nèi)容如下所示:

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

假設(shè)此時(shí) Thread-0 釋放鎖,就會(huì)進(jìn)入到 tryRelease() 方法,如果返回 true,就證明釋放成功了,就會(huì)把owner 線程設(shè)置為 null,并且把 state 設(shè)置為 0,如下圖:

tryRelease() 方法返回 true 之后,還需要檢查隊(duì)列中的 head 是否為空,還需要檢查 head 中的 waitState 是否不等于 0 ,若返回 true ,則需要調(diào)用 unparkSuccessor() 方法喚醒下一個(gè)節(jié)點(diǎn)。

unparkSuccessor() 方法是找到隊(duì)列中離 head 最近的一個(gè) Node(沒取消的),unpark 恢復(fù)其運(yùn)行,本例中即為 Thread-1,此時(shí)回到 Thread-1 acquireQueued() 方法的流程,如下圖:

如果加鎖成功(沒有競(jìng)爭(zhēng)),會(huì)設(shè)置 exclusiveOwnerThread Thread-1,state = 1;head 指向剛剛 Thread-1 所在的 Node,該 Node 清空 Thread;原本的 head 因?yàn)閺逆湵頂嚅_,而可被垃圾回收。

如果這時(shí)候有其它線程來競(jìng)爭(zhēng)(非公平的體現(xiàn)),例如這時(shí)有 Thread-4 來了,Thread-4 并不是阻塞隊(duì)列里面的線程,如下圖:

如果不巧又被 Thread-4 占了先,那么 Thread-4 被設(shè)置為 exclusiveOwnerThread,state = 1;Thread-1 再次進(jìn)入 acquireQueued 流程,獲取鎖失敗,重新進(jìn)入 park 阻塞。

三、可重入原理

接下來我們看下非公平鎖的可重入的實(shí)現(xiàn)原理,我們看下他的獲取鎖和釋放鎖的相關(guān)代碼,

獲取鎖最終調(diào)用的是 nonfairTryAcquire() 方法,傳入的 acquires 就是 1,首先判斷 state 的狀態(tài)是否是 0,如果是 0 就代表還沒有別人獲取鎖,然后我就去 CAS ,如果成功則設(shè)置 owner 設(shè)置為自己,然后返回 true。

如果是同一個(gè)線程下次又調(diào)用nonfairTryAcquire() 方法,此時(shí)就回去判斷當(dāng)前線程是否為 owner 持有的線程,如果是,則證明發(fā)生了鎖重入,此時(shí)就是將 state 的值 ++,由 1 變成了 2,

static final class NonfairSync extends Sync {
    // ...

    // Sync 繼承過來的方法, 方便閱讀, 放在此處
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果已經(jīng)獲得了鎖, 線程還是當(dāng)前線程, 表示發(fā)生了鎖重入
        else if (current == getExclusiveOwnerThread()) {
            // state++
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

鎖釋放的時(shí)候調(diào)用的是 tryRelease() 方法,如下所示,此時(shí)就會(huì)把 state 的值 --,此時(shí)返回一個(gè) false,表示我只是讓 state -- ,并沒有真正的釋放鎖,直到下面的這個(gè) c 變成 0 了,才會(huì)釋放鎖,返回 true

    // Sync 繼承過來的方法, 方便閱讀, 放在此處
    protected final boolean tryRelease(int releases) {
        // state--
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 支持鎖重入, 只有 state 減為 0, 才釋放成功
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

四、可打斷原理

4.1 不可打斷模式

在次模式下,即使它被打斷,仍會(huì)駐留在 AQS 隊(duì)列中,一直要等到獲得鎖后方能得知自己被打斷了。

我們都知道線程在知道自己沒有辦法獲取鎖的時(shí)候就會(huì)進(jìn)入到 acquireQueued() 方法的循環(huán)內(nèi)不斷的去嘗試,如果還不成功,就會(huì)進(jìn)入到parkAndCheckInterrupt() 方法里面等待。

但是進(jìn)入到 parkAndCheckInterrupt() 方法的線程可以被其他的線程調(diào)用它的 interrupted() 方法喚醒,如下面的parkAndCheckInterrupt() 方法,會(huì)返回一個(gè) boolean 表示是否被打斷過,但是interrupted() 方法還會(huì)清除打斷標(biāo)記,即下次再 park 還可以 park 住。

如果parkAndCheckInterrupt() 返回 true 了就會(huì)進(jìn)入 if 塊了,此時(shí)就會(huì)對(duì)打斷標(biāo)記做一個(gè)記錄,設(shè)置值為 true,沒有做額外的處理,接下來還是會(huì)繼續(xù) for 循環(huán),獲取不了鎖還是會(huì) park 阻塞。只有當(dāng)你獲取到鎖以后,會(huì)把打斷標(biāo)記作為結(jié)果返回。

此時(shí)就會(huì)進(jìn)入到selfInterrupt() 方法,重新產(chǎn)生一次中斷。

static final class NonfairSync extends Sync {
    // ...

    private final boolean parkAndCheckInterrupt() {
        // 如果打斷標(biāo)記已經(jīng)是 true, 則 park 會(huì)失效
        LockSupport.park(this);
        // interrupted 會(huì)清除打斷標(biāo)記
        return Thread.interrupted();
    }

    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;
                    failed = false;
                    // 還是需要獲得鎖后, 才能返回打斷狀態(tài)
                    return interrupted;
                }
                if (
                        shouldParkAfterFailedAcquire(p, node) &&
                                parkAndCheckInterrupt()
                ) {
                    // 如果是因?yàn)?interrupt 被喚醒, 返回打斷狀態(tài)為 true
                    interrupted = true;
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    public final void acquire(int arg) {
        if (
                !tryAcquire(arg) &&
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            // 如果打斷狀態(tài)為 true
            selfInterrupt();
        }
    }

    static void selfInterrupt() {
        // 重新產(chǎn)生一次中斷
        Thread.currentThread().interrupt();
    }
}

4.2 可打斷模式

可打斷模式就是在調(diào)用acquireInterruptibly() 方法獲取鎖時(shí)可以被打斷,它和上面的方法區(qū)別是當(dāng)線程被喚醒的時(shí)候,即調(diào)用doAcquireInterruptibly() 時(shí)會(huì)拋出一個(gè)異常,打斷 for 循環(huán)等待,即線程停止去等待鎖

// Sync 繼承自 AQS
static final class NonfairSync extends Sync {
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 如果沒有獲得到鎖, 進(jìn)入 ㈠
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    // ㈠ 可打斷的獲取鎖流程
    private void doAcquireInterruptibly(int arg) throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    // 在 park 過程中如果被 interrupt 會(huì)進(jìn)入此
                    // 這時(shí)候拋出異常, 而不會(huì)再次進(jìn)入 for (;;)
                    throw new InterruptedException();
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
}

五、公平鎖實(shí)現(xiàn)原理

公平鎖和非公平鎖的區(qū)別主要就是在 tryAcquire() 方法的實(shí)現(xiàn)中,當(dāng) c==0 即還沒有別人占這個(gè)鎖時(shí),先調(diào)用 hasQueuedPredecessors() 方法判斷隊(duì)列中是否有前驅(qū)節(jié)點(diǎn),沒有才回去競(jìng)爭(zhēng),即只能是老二節(jié)點(diǎn)線程才可以運(yùn)行,非老二節(jié)點(diǎn)線程不能運(yùn)行。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    // AQS 繼承過來的方法, 方便閱讀, 放在此處
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            selfInterrupt();
        }
    }

    // 與非公平鎖主要區(qū)別在于 tryAcquire 方法的實(shí)現(xiàn)
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 先檢查 AQS 隊(duì)列中是否有前驅(qū)節(jié)點(diǎn), 沒有才去競(jìng)爭(zhēng)
            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;
    }

    // ㈠ AQS 繼承過來的方法, 方便閱讀, 放在此處
    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        // h != t 時(shí)表示隊(duì)列中有 Node
        return h != t &&
                (
                        // (s = h.next) == null 表示隊(duì)列中還有沒有老二
                        (s = h.next) == null ||
                                // 或者隊(duì)列中老二線程不是此線程
                                s.thread != Thread.currentThread()
                );
    }
}

六、條件變量實(shí)現(xiàn)原理

每個(gè)條件變量其實(shí)就對(duì)應(yīng)著一個(gè)等待隊(duì)列,其實(shí)現(xiàn)類就是 ConditionObject。

6.1 await 流程

假設(shè)一開始 Thread-0 持有鎖,如下圖所示:

接下來如果線程調(diào)用了ConditionObject 類的 await() 方法,它就會(huì)進(jìn)入到ConditionObject addConditionWaiter 流程,如下圖:

addConditionWaiter () 方法會(huì)把調(diào)用的線程加入到條件變量里面的雙向鏈表之中,并且把新的 Node 狀態(tài)變?yōu)?strong> -2,表示在條件變量中處于等待狀態(tài),如下圖:

接下來會(huì)進(jìn)入到AQS fullyRelease 流程,釋放同步器上的鎖,如下圖:

接下來會(huì) unpark AQS 隊(duì)列中的下一個(gè)節(jié)點(diǎn),競(jìng)爭(zhēng)鎖,假設(shè)沒有其他競(jìng)爭(zhēng)線程,那么 Thread-1 競(jìng)爭(zhēng)成功,如下圖所示:

此時(shí) Thread-0 的狀態(tài)就被 park 阻塞了,如下圖:

6.2 signal 流程

假設(shè)此時(shí) Thread-1 要來喚醒 Thread-0,如下圖:

signal() 方法的代碼如下所示,首先進(jìn)行健康檢查,看調(diào)用方法的線程是否為鎖的持有者,即只有 owner 才可以喚醒其他線程,接下來查找條件變量列表中的第一個(gè)元素,查找完畢之后調(diào)用 doSignal() 方法。

doSignal() 方法是取得等待隊(duì)列中第一個(gè) Node,即 Thread-0 所在 Node

此時(shí)的狀態(tài)圖如下所示:

獲取完Node 之后,調(diào)用 transferForSignal() 方法,將該 Node 加入 AQS 隊(duì)列尾部,將 Thread-0 waitStatus 改為 0,Thread-3 waitStatus 改為 -1

接下來就是 Thread-1 釋放鎖,把 state 置為 0,設(shè)置 owner null,喚醒等待隊(duì)列中的下一個(gè)元素了,整個(gè)流程結(jié)束。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • java實(shí)現(xiàn)斗地主發(fā)牌功能

    java實(shí)現(xiàn)斗地主發(fā)牌功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)斗地主發(fā)牌功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • Java的垃圾回收機(jī)制實(shí)例分析

    Java的垃圾回收機(jī)制實(shí)例分析

    這篇文章主要介紹了Java的垃圾回收機(jī)制,結(jié)合實(shí)例形式分析了垃圾回收機(jī)制的原理及相關(guān)操作技巧,需要的朋友可以參考下
    2019-08-08
  • Spring監(jiān)聽器及定時(shí)任務(wù)實(shí)現(xiàn)方法詳解

    Spring監(jiān)聽器及定時(shí)任務(wù)實(shí)現(xiàn)方法詳解

    這篇文章主要介紹了Spring監(jiān)聽器及定時(shí)任務(wù)實(shí)現(xiàn)方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java中finalize()詳解及用法

    Java中finalize()詳解及用法

    這篇文章主要介紹了Java中finalize()詳解及用法的相關(guān)資料,final是Java的關(guān)鍵字,它所表示的是“這部分是無法修改的”,需要的朋友可以參考下
    2017-03-03
  • 深入理解Java Optional使用實(shí)踐記錄

    深入理解Java Optional使用實(shí)踐記錄

    Java8引入Optional類,用于優(yōu)雅處理可能為空的值,避免空指針異常,提供創(chuàng)建、轉(zhuǎn)換、過濾方法,支持鏈?zhǔn)秸{(diào)用,但應(yīng)避免作為字段類型或集合元素,以防性能損耗,本文給大家介紹Java Optional使用實(shí)踐記錄,感興趣的朋友一起看看吧
    2025-06-06
  • java實(shí)現(xiàn)微信支付功能

    java實(shí)現(xiàn)微信支付功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信支付功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • Rxjava+Retrofit+MVP實(shí)現(xiàn)購(gòu)物車功能

    Rxjava+Retrofit+MVP實(shí)現(xiàn)購(gòu)物車功能

    這篇文章主要為大家詳細(xì)介紹了Rxjava+Retrofit+MVP實(shí)現(xiàn)購(gòu)物車功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Springboot實(shí)現(xiàn)獲取實(shí)時(shí)天氣

    Springboot實(shí)現(xiàn)獲取實(shí)時(shí)天氣

    這篇文章主要為大家詳細(xì)介紹了如何使用Springboot實(shí)現(xiàn)獲取實(shí)時(shí)天氣功能,文中的示例代碼講解詳細(xì),有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • Java JDBC數(shù)據(jù)庫連接失敗的7種常見原因及解決方案

    Java JDBC數(shù)據(jù)庫連接失敗的7種常見原因及解決方案

    Java JDBC(Java Database Connectivity)是Java平臺(tái)中用于執(zhí)行SQL語句的標(biāo)準(zhǔn)API,它為開發(fā)者提供了與各種關(guān)系型數(shù)據(jù)庫進(jìn)行交互的能力,本文為大家介紹了Java JDBC數(shù)據(jù)庫連接失敗的7種常見原因及解決方案,需要的朋友可以參考下
    2025-11-11
  • 詳解Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀

    詳解Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀

    這篇文章主要介紹了Java的TCP/IP編程學(xué)習(xí)--基于定界符的成幀,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04

最新評(píng)論

双流县| 信阳市| 嘉鱼县| 扎赉特旗| 金川县| 安西县| 眉山市| 昆山市| 库尔勒市| 焉耆| 玛纳斯县| 双牌县| 高尔夫| 成都市| 阿巴嘎旗| 象山县| 娄底市| 壤塘县| 青川县| 灵石县| 抚州市| 邻水| 从化市| 个旧市| 怀仁县| 临潭县| 昌平区| 铜山县| 兰州市| 长春市| 兰西县| 绥中县| 临朐县| 武城县| 鲁山县| 福鼎市| 南康市| 宿迁市| 富顺县| 井陉县| 永定县|