中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java同步器AQS架構AbstractQueuedSynchronizer原理是什么

發布時間:2022-03-14 09:25:57 來源:億速云 閱讀:144 作者:iii 欄目:開發技術

這篇文章主要介紹“java同步器AQS架構AbstractQueuedSynchronizer原理是什么”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“java同步器AQS架構AbstractQueuedSynchronizer原理是什么”文章能幫助大家解決問題。

    引導語

    AbstractQueuedSynchronizer 中文翻譯叫做同步器,簡稱 AQS,是各種各樣鎖的基礎,比如說 ReentrantLock、CountDownLatch 等等,這些我們經常用的鎖底層實現都是 AQS,所以學好 AQS 對于后面理解鎖的實現是非常重要的。

    鎖章節的內容是這么安排的:

    1:AQS 源碼非常多,我們會分成兩個小節來說,先把底層原理弄清楚;

    2:我們平時用不到 AQS,只會接觸到 ReentrantLock、CountDownLatch 這些鎖,我們以兩個鎖為例子,講解下源碼,因為 AQS 只要弄懂了,所有的鎖你只要清楚鎖的目的,就能夠利用 AQS 去實現它;

    3:總結一下鎖的面試題;

    4:總結一下鎖在工作中有哪些使用場景,舉幾個實際的例子,看看鎖使用時,有哪些注意事項;

    5:最后我們自己來實現一個鎖,看看如果我們自己來實現鎖,有哪些步驟,需要注意哪些事項。

    ps:本章內容需要大量隊列基礎知識,沒有看過第四章節隊列的同學,建議先閱讀下隊列章節。

    1、整體架構

    首先我們來看一下 AQS 的整體架構圖,如下:

    java同步器AQS架構AbstractQueuedSynchronizer原理是什么

     這個圖總結了 AQS 整體架構的組成,和部分場景的動態流向,圖中兩個點說明一下,方便大家觀看。

    • AQS 中隊列只有兩個:同步隊列 + 條件隊列,底層數據結構兩者都是鏈表;

    • 圖中有四種顏色的線代表四種不同的場景,1、2、3 序號代表看的順序。

     AQS 本身就是一套鎖的框架,它定義了獲得鎖和釋放鎖的代碼結構,所以如果要新建鎖,只要繼承 AQS,并實現相應方法即可。

    接下來我們一起來看下這個圖中各個細節點。

    1.1、類注釋

    首先我們來看一下,從 AQS 類注釋上,我們可以得到哪些信息:

    1. 提供了一種框架,自定義了先進先出的同步隊列,讓獲取不到鎖的線程能進入同步隊列中排隊;

    2. 同步器有個狀態字段,我們可以通過狀態字段來判斷能否得到鎖,此時設計的關鍵在于依賴安全的 atomic value 來表示狀態(雖然注釋是這個意思,但實際上是通過把狀態聲明為 volatile,在鎖里面修改狀態值來保證線程安全的);

    3. 子類可以通過給狀態 CAS 賦值來決定能否拿到鎖,可以定義那些狀態可以獲得鎖,哪些狀態表示獲取不到鎖(比如定義狀態值是 0 可以獲得鎖,狀態值是 1 就獲取不到鎖);

    4. 子類可以新建非 public 的內部類,用內部類來繼承 AQS,從而實現鎖的功能;

    5. AQS 提供了排它模式和共享模式兩種鎖模式。排它模式下:只有一個線程可以獲得鎖,共享模式可以讓多個線程獲得鎖,子類 ReadWriteLock 實現了兩種模式;

    6. 內部類 ConditionObject 可以被用作 Condition,我們通過 new ConditionObject () 即可得到條件隊列;

    7. AQS 實現了鎖、排隊、鎖隊列等框架,至于如何獲得鎖、釋放鎖的代碼并沒有實現,比如 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 這些方法,AQS 中默認拋 UnsupportedOperationException 異常,都是需要子類去實現的;

    8. AQS 繼承 AbstractOwnableSynchronizer 是為了方便跟蹤獲得鎖的線程,可以幫助監控和診斷工具識別是哪些線程持有了鎖;

    9. AQS 同步隊列和條件隊列,獲取不到鎖的節點在入隊時是先進先出,但被喚醒時,可能并不會按照先進先出的順序執行。

    AQS 的注釋還有很多很多,以上 9 點是挑選出來稍微比較重要的注釋總結。

    1.2、類定義

    AQS 類定義代碼如下:

    public abstract class AbstractQueuedSynchronizer
        extends AbstractOwnableSynchronizer
        implements java.io.Serializable {

    可以看出兩點:

    AQS 是個抽象類,就是給各種鎖子類繼承用的,AQS 定義了很多如何獲得鎖,如何釋放鎖的抽象方法,目的就是為了讓子類去實現;

    繼承了 AbstractOwnableSynchronizer,AbstractOwnableSynchronizer 的作用就是為了知道當前是那個線程獲得了鎖,方便監控用的,

    代碼如下:

    java同步器AQS架構AbstractQueuedSynchronizer原理是什么

    1.3、基本屬性

    AQS 的屬性可簡單分為四類:同步器簡單屬性、同步隊列屬性、條件隊列屬性、公用 Node。

    1.3.1、簡單屬性

    首先我們來看一下簡單屬性有哪些:

    // 同步器的狀態,子類會根據狀態字段進行判斷是否可以獲得鎖
    // 比如 CAS 成功給 state 賦值 1 算得到鎖,賦值失敗為得不到鎖, CAS 成功給 state 賦值 0 算釋放鎖,賦值失敗為釋放失敗
    // 可重入鎖,每次獲得鎖 +1,每次釋放鎖 -1
    private volatile int state;
     
    // 自旋超時閥值,單位納秒
    // 當設置等待時間時才會用到這個屬性
    static final long spinForTimeoutThreshold = 1000L;

    最重要的就是 state 屬性,是 int 屬性的,所有繼承 AQS 的鎖都是通過這個字段來判斷能不能獲得鎖,能不能釋放鎖。

    1.3.2 、同步隊列屬性

    首先我們介紹以下同步隊列:當多個線程都來請求鎖時,某一時刻有且只有一個線程能夠獲得鎖(排它鎖),那么剩余獲取不到鎖的線程,都會到同步隊列中去排隊并阻塞自己,當有線程主動釋放鎖時,就會從同步隊列頭開始釋放一個排隊的線程,讓線程重新去競爭鎖。

    所以同步隊列的主要作用阻塞獲取不到鎖的線程,并在適當時機釋放這些線程。

    同步隊列底層數據結構是個雙向鏈表,我們從源碼中可以看到鏈表的頭尾,如下:

    // 同步隊列的頭。
    private transient volatile Node head;
    // 同步隊列的尾
    private transient volatile Node tail;

    源碼中的 Node 是同步隊列中的元素,但 Node 被同步隊列和條件隊列公用,所以我們在說完條件隊列之后再說 Node。

    1.3.3、條件隊列的屬性

    首先我們介紹下條件隊列:條件隊列和同步隊列的功能一樣,管理獲取不到鎖的線程,底層數據結構也是鏈表隊列,但條件隊列不直接和鎖打交道,但常常和鎖配合使用,是一定的場景下,對鎖功能的一種補充。

    條件隊列的屬性如下:

    // 條件隊列,從屬性上可以看出是鏈表結構
    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        // 條件隊列中第一個 node
        private transient Node firstWaiter;
        // 條件隊列中最后一個 node
        private transient Node lastWaiter;
    }

    ConditionObject 我們就稱為條件隊列,我們需要使用時,直接 new ConditionObject () 即可。

    ConditionObject 是實現 Condition 接口的,Condition 接口相當于 Object 的各種監控方法,比如 Object#wait ()、Object#notify、Object#notifyAll 這些方法,我們可以先這么理解,后面會細說。

    1.3.4、Node

    Node 非常重要,即是同步隊列的節點,又是條件隊列的節點,在入隊的時候,我們用 Node 把線程包裝一下,然后把 Node 放入兩個隊列中,我們看下 Node 的數據結構,如下:

    static final class Node {
        /**
         * 同步隊列單獨的屬性
         */
        //node 是共享模式
        static final Node SHARED = new Node();
        //node 是排它模式
        static final Node EXCLUSIVE = null;
        // 當前節點的前節點
        // 節點 acquire 成功后就會變成head
        // head 節點不能被 cancelled
        volatile Node prev;
        // 當前節點的下一個節點
        volatile Node next;
        /**
         * 兩個隊列共享的屬性
         */
        // 表示當前節點的狀態,通過節點的狀態來控制節點的行為
        // 普通同步節點,就是 0 ,條件節點是 CONDITION -2
        volatile int waitStatus;
        // waitStatus 的狀態有以下幾種
        // 被取消
        static final int CANCELLED =  1;
        // SIGNAL 狀態的意義:同步隊列中的節點在自旋獲取鎖的時候,如果前一個節點的狀態是 SIGNAL,那么自己就可以阻塞休息了,否則自己一直自旋嘗試獲得鎖
        static final int SIGNAL    = -1;
        // 表示當前 node 正在條件隊列中,當有節點從同步隊列轉移到條件隊列時,狀態就會被更改成 CONDITION
        static final int CONDITION = -2;
        // 無條件傳播,共享模式下,該狀態的進程處于可運行狀態
        static final int PROPAGATE = -3;
        // 當前節點的線程
        volatile Thread thread;
        // 在同步隊列中,nextWaiter 并不真的是指向其下一個節點,我們用 next 表示同步隊列的下一個節點,nextWaiter 只是表示當前 Node 是排它模式還是共享模式
        // 但在條件隊列中,nextWaiter 就是表示下一個節點元素
        Node nextWaiter;
    }

    從 Node 的結構中,我們需要重點關注 waitStatus 字段,Node 的很多操作都是圍繞著 waitStatus 字段進行的。

    Node 的 pre、next 屬性是同步隊列中的鏈表前后指向字段,nextWaiter 是條件隊列中下一個節點的指向字段,但在同步隊列中,nextWaiter 只是一個標識符,表示當前節點是共享還是排它模式。

    1.3.5、共享鎖和排它鎖的區別

    排它鎖的意思是同一時刻,只能有一個線程可以獲得鎖,也只能有一個線程可以釋放鎖。

    共享鎖可以允許多個線程獲得同一個鎖,并且可以設置獲取鎖的線程數量。

    1.4、Condition

    剛才我們看條件隊列 ConditionObject 時,發現其是實現 Condition 接口的,現在我們一起來看下 Condition 接口,其類注釋上是這么寫的:

    1. 當 lock 代替 synchronized 來加鎖時,Condition 就可以用來代替 Object 中相應的監控方法了,比如 Object#wait ()、Object#notify、Object#notifyAll 這些方法;

    2. 提供了一種線程協作方式:一個線程被暫停執行,直到被其它線程喚醒;

    3. Condition 實例是綁定在鎖上的,通過 Lock#newCondition 方法可以產生該實例;

    4. 除了特殊說明外,任意空值作為方法的入參,都會拋出空指針;

    5. Condition 提供了明確的語義和行為,這點和 Object 監控方法不同。

    類注釋上甚至還給我們舉了一個例子:

    假設我們有一個有界邊界的隊列,支持 put 和 take 方法,需要滿足:

    1:如果試圖往空隊列上執行 take,線程將會阻塞,直到隊列中有可用的元素為止;

    2:如果試圖往滿的隊列上執行 put,線程將會阻塞,直到隊列中有空閑的位置為止。

    1、2 中線程阻塞都會到條件隊列中去阻塞。

    take 和 put 兩種操作如果依靠一個條件隊列,那么每次只能執行一種操作,所以我們可以新建兩個條件隊列,這樣就可以分別執行操作了,看了這個需求,是不是覺得很像我們第三章學習的隊列?實際上注釋上給的 demo 就是我們學習過的隊列,篇幅有限,感興趣的可以看看 ConditionDemo 這個測試類。

    除了類注釋,Condition 還定義出一些方法,這些方法奠定了條件隊列的基礎,方法主要有:

    void await() throws InterruptedException;

    這個方法的主要作用是:使當前線程一直等待,直到被 signalled 或被打斷。

    當以下四種情況發生時,條件隊列中的線程將被喚醒

    • 有線程使用了 signal 方法,正好喚醒了條件隊列中的當前線程;

    • 有線程使用了 signalAll 方法;

    • 其它線程打斷了當前線程,并且當前線程支持被打斷;

    • 被虛假喚醒 (即使沒有滿足以上 3 個條件,wait 也是可能被偶爾喚醒,虛假喚醒定義可以參考: https://en.wikipedia.org/wiki/Spurious_wakeup)。

    被喚醒時,有一點需要注意的是:線程從條件隊列中蘇醒時,必須重新獲得鎖,才能真正被喚醒,這個我們在說源碼的時候,也會強調這個。

    await 方法還有帶等待超時時間的,如下:

    // 返回的 long 值表示剩余的給定等待時間,如果返回的時間小于等于 0 ,說明等待時間過了
    // 選擇納秒是為了避免計算剩余等待時間時的截斷誤差
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    // 雖然入參可以是任意單位的時間,但底層仍然轉化成納秒
    boolean await(long time, TimeUnit unit) throws InterruptedException;

    除了等待方法,還是喚醒線程的兩個方法,如下:

    // 喚醒條件隊列中的一個線程,在被喚醒前必須先獲得鎖
    void signal();
    // 喚醒條件隊列中的所有線程
    void signalAll();

    至此,AQS 基本的屬性就已經介紹完了,接著讓我們來看一看 AQS 的重要方法。

    2、同步器的狀態

    在同步器中,我們有兩個狀態,一個叫做 state,一個叫做 waitStatus,兩者是完全不同的概念:

    state 是鎖的狀態,是 int 類型,子類繼承 AQS 時,都是要根據 state 字段來判斷有無得到鎖,比如當前同步器狀態是 0,表示可以獲得鎖,當前同步器狀態是 1,表示鎖已經被其他線程持有,當前線程無法獲得鎖;

    waitStatus 是節點(Node)的狀態,種類很多,一共有初始化 (0)、CANCELLED (1)、SIGNAL (-1)、CONDITION (-2)、PROPAGATE (-3),各個狀態的含義可以見上文。

    這兩個狀態我們需要牢記,不要混淆了。

    3、獲取鎖

    獲取鎖最直觀的感受就是使用 Lock.lock () 方法來獲得鎖,最終目的是想讓線程獲得對資源的訪問權。

    Lock 一般是 AQS 的子類,lock 方法根據情況一般會選擇調用 AQS 的 acquire 或 tryAcquire 方法。

    acquire 方法 AQS 已經實現了,tryAcquire 方法是等待子類去實現,acquire 方法制定了獲取鎖的框架,先嘗試使用 tryAcquire 方法獲取鎖,獲取不到時,再入同步隊列中等待鎖。tryAcquire 方法 AQS 中直接拋出一個異常,表明需要子類去實現,子類可以根據同步器的 state 狀態來決定是否能夠獲得鎖,接下來我們詳細看下 acquire 的源碼解析。

    acquire 也分兩種,一種是排它鎖,一種是共享鎖,我們一一來看下:

    3.1、acquire 排它鎖

    // 排它模式下,嘗試獲得鎖
    public final void acquire(int arg) {
        // tryAcquire 方法是需要實現類去實現的,實現思路一般都是 cas 給 state 賦值來決定是否能獲得鎖
        if (!tryAcquire(arg) &&
            // addWaiter 入參代表是排他模式
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

    以上代碼的主要步驟是(流程見整體架構圖中紅色場景):

    嘗試執行一次 tryAcquire,如果成功直接返回,失敗走 2;線程嘗試進入同步隊列,首先調用 addWaiter 方法,把當前線程放到同步隊列的隊尾;接著調用 acquireQueued 方法,兩個作用,1:阻塞當前節點,2:節點被喚醒時,使其能夠獲得鎖;如果 2、3 失敗了,打斷線程。

    3.1.1、addWaiter

    代碼很少,每個方法都是關鍵,接下來我們先來看下 addWaiter 的源碼實現:

    // 方法主要目的:node 追加到同步隊列的隊尾
    // 入參 mode 表示 Node 的模式(排它模式還是共享模式)
    // 出參是新增的 node
    // 主要思路:
    // 新 node.pre = 隊尾
    // 隊尾.next = 新 node
    private Node addWaiter(Node mode) {
        // 初始化 Node
        Node node = new Node(Thread.currentThread(), mode);
        // 這里的邏輯和 enq 一致,enq 的邏輯僅僅多了隊尾是空,初始化的邏輯
        // 這個思路在 java 源碼中很常見,先簡單的嘗試放一下,成功立馬返回,如果不行,再 while 循環
        // 很多時候,這種算法可以幫忙解決大部分的問題,大部分的入隊可能一次都能成功,無需自旋
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //自旋保證node加入到隊尾
        enq(node);
        return node;
    }
    // 線程加入同步隊列中方法,追加到隊尾
    // 這里需要重點注意的是,返回值是添加 node 的前一個節點
    private Node enq(final Node node) {
        for (;;) {
            // 得到隊尾節點
            Node t = tail;
            // 如果隊尾為空,說明當前同步隊列都沒有初始化,進行初始化
            // tail = head = new Node();
            if (t == null) {
                if (compareAndSetHead(new Node()))
                    tail = head;
            // 隊尾不為空,將當前節點追加到隊尾
            } else {
                node.prev = t;
                // node 追加到隊尾
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    如果之前學習過隊列的同學,對這個方法應該感覺毫不吃力,就是把新的節點追加到同步隊列的隊尾。

    其中有一點值得我們學習的地方,是在 addWaiter 方法中,并沒有進入方法后立馬就自旋,而是先嘗試一次追加到隊尾,如果失敗才自旋,因為大部分操作可能一次就會成功,這種思路在我們寫自旋的時候可以借鑒。

    3.1.2、acquireQueued

    下一步就是要阻塞當前線程了,是 acquireQueued 方法來實現的,我們來看下源碼實現:

    // 主要做兩件事情:
    // 1:通過不斷的自旋嘗試使自己前一個節點的狀態變成 signal,然后阻塞自己。
    // 2:獲得鎖的線程執行完成之后,釋放鎖時,會把阻塞的 node 喚醒,node 喚醒之后再次自旋,嘗試獲得鎖
    // 返回 false 表示獲得鎖成功,返回 true 表示失敗
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 自旋
            for (;;) {
                // 選上一個節點
                final Node p = node.predecessor();
                // 有兩種情況會走到 p == head:
                // 1:node 之前沒有獲得鎖,進入 acquireQueued 方法時,才發現他的前置節點就是頭節點,于是嘗試獲得一次鎖;
                // 2:node 之前一直在阻塞沉睡,然后被喚醒,此時喚醒 node 的節點正是其前一個節點,也能走到 if
                // 如果自己 tryAcquire 成功,就立馬把自己設置成 head,把上一個節點移除
                // 如果 tryAcquire 失敗,嘗試進入同步隊列
                if (p == head && tryAcquire(arg)) {
                    // 獲得鎖,設置成 head 節點
                    setHead(node);
                    //p被回收
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // shouldParkAfterFailedAcquire 把 node 的前一個節點狀態置為 SIGNAL
                // 只要前一個節點狀態是 SIGNAL了,那么自己就可以阻塞(park)了
                // parkAndCheckInterrupt 阻塞當前線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    // 線程是在這個方法里面阻塞的,醒來的時候仍然在無限 for 循環里面,就能再次自旋嘗試獲得鎖
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 如果獲得node的鎖失敗,將 node 從隊列中移除
            if (failed)
                cancelAcquire(node);
        }
    }

    此方法的注釋還是很清楚的,我們接著看下此方法的核心:shouldParkAfterFailedAcquire,這個方法的主要目的就是把前一個節點的狀態置為 SIGNAL,只要前一個節點的狀態是 SIGNAL,當前節點就可以阻塞了(parkAndCheckInterrupt 就是使節點阻塞的方法),

    源碼如下:

    // 當前線程可以安心阻塞的標準,就是前一個節點線程狀態是 SIGNAL 了。
    // 入參 pred 是前一個節點,node 是當前節點。
    // 關鍵操作:
    // 1:確認前一個節點是否有效,無效的話,一直往前找到狀態不是取消的節點。
    // 2: 把前一個節點狀態置為 SIGNAL。
    // 1、2 兩步操作,有可能一次就成功,有可能需要外部循環多次才能成功(外面是個無限的 for 循環),但最后一定是可以成功的
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 如果前一個節點 waitStatus 狀態已經是 SIGNAL 了,直接返回,不需要在自旋了
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        // 如果當前節點狀態已經被取消了。
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            // 找到前一個狀態不是取消的節點,因為把當前 node 掛在有效節點身上
            // 因為節點狀態是取消的話,是無效的,是不能作為 node 的前置節點的,所以必須找到 node 的有效節點才行
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        // 否則直接把節點狀態置 為SIGNAL
        } else {
            /*
             * 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;
    }

    acquire 整個過程非常長,代碼也非常多,但注釋很清楚,可以一行一行仔細看看代碼。

    總結一下,acquire 方法大致分為三步:

    • 使用 tryAcquire 方法嘗試獲得鎖,獲得鎖直接返回,獲取不到鎖的走 2;

    • 把當前線程組裝成節點(Node),追加到同步隊列的尾部(addWaiter);

    • 自旋,使同步隊列中當前節點的前置節點狀態為 signal 后,然后阻塞自己。

    整體的代碼結構比較清晰,一些需要注意的點,都用注釋表明了,強烈建議閱讀下源碼。

    3.2、acquireShared 獲取共享鎖

    acquireShared 整體流程和 acquire 相同,代碼也很相似,重復的源碼就不貼了,我們就貼出來不一樣的代碼來,也方便大家進行比較:

    第一步嘗試獲得鎖的地方,有所不同,排它鎖使用的是 tryAcquire 方法,共享鎖使用的是 tryAcquireShared 方法,如下圖:

    java同步器AQS架構AbstractQueuedSynchronizer原理是什么

    第二步不同,在于節點獲得排它鎖時,僅僅把自己設置為同步隊列的頭節點即可(setHead 方法),但如果是共享鎖的話,還會去喚醒自己的后續節點,一起來獲得該鎖(setHeadAndPropagate 方法),不同之處如下(左邊排它鎖,右邊共享鎖):

    java同步器AQS架構AbstractQueuedSynchronizer原理是什么

     接下來我們一起來看下 setHeadAndPropagate 方法的源碼:

    // 主要做兩件事情
    // 1:把當前節點設置成頭節點
    // 2:看看后續節點有無正在等待,并且也是共享模式的,有的話喚醒這些節點
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        // 當前節點設置成頭節點
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated(表示指示) by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism(保守) in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        // propagate > 0 表示已經有節點獲得共享鎖了
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //共享模式,還喚醒頭節點的后置節點
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
    // 釋放后置共享節點
    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            // 還沒有到隊尾,此時隊列中至少有兩個節點
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 如果隊列狀態是 SIGNAL ,說明后續節點都需要喚醒
                if (ws == Node.SIGNAL) {
                    // CAS 保證只有一個節點可以運行喚醒的操作
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 進行喚醒操作
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            // 第一種情況,頭節點沒有發生移動,結束。
            // 第二種情況,因為此方法可以被兩處調用,一次是獲得鎖的地方,一處是釋放鎖的地方,
            // 加上共享鎖的特性就是可以多個線程獲得鎖,也可以釋放鎖,這就導致頭節點可能會發生變化,
            // 如果頭節點發生了變化,就繼續循環,一直循環到頭節點不變化時,結束循環。
            if (h == head)                   // loop if head changed
                break;
        }
    }

    這個就是共享鎖獨特的地方,當一個線程獲得鎖后,它就會去喚醒排在它后面的其它節點,讓其它節點也能夠獲得鎖。

    關于“java同步器AQS架構AbstractQueuedSynchronizer原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    溧阳市| 定兴县| 安乡县| 读书| 宁陕县| 柳江县| 商河县| 藁城市| 巴彦淖尔市| 镇远县| 平阳县| 塘沽区| 台南县| 郎溪县| 额济纳旗| 鄯善县| 昌邑市| 乌海市| 蒲江县| 札达县| 河曲县| 宁安市| 高淳县| 金寨县| 东平县| 盱眙县| 舟山市| 静乐县| 静海县| 龙川县| 南昌市| 米泉市| 南澳县| 微山县| 屏边| 长沙县| 浙江省| 山阴县| 东平县| 博湖县| 广德县|