欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java同步器AQS架構(gòu)AbstractQueuedSynchronizer原理解析

 更新時(shí)間:2022年03月11日 16:59:14   作者:Q.E.D  
這篇文章主要為大家介紹了java同步器AQS架構(gòu)AbstractQueuedSynchronizer的底層原理及源碼解析,有需要的朋友可以借鑒參考下,希望能有所幫助,祝大家多多進(jìn)步早日升職加薪

引導(dǎo)語

AbstractQueuedSynchronizer 中文翻譯叫做同步器,簡稱 AQS,是各種各樣鎖的基礎(chǔ),比如說 ReentrantLock、CountDownLatch 等等,這些我們經(jīng)常用的鎖底層實(shí)現(xiàn)都是 AQS,所以學(xué)好 AQS 對(duì)于后面理解鎖的實(shí)現(xiàn)是非常重要的。

鎖章節(jié)的內(nèi)容是這么安排的:

1:AQS 源碼非常多,我們會(huì)分成兩個(gè)小節(jié)來說,先把底層原理弄清楚;

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

3:總結(jié)一下鎖的面試題;

4:總結(jié)一下鎖在工作中有哪些使用場(chǎng)景,舉幾個(gè)實(shí)際的例子,看看鎖使用時(shí),有哪些注意事項(xiàng);

5:最后我們自己來實(shí)現(xiàn)一個(gè)鎖,看看如果我們自己來實(shí)現(xiàn)鎖,有哪些步驟,需要注意哪些事項(xiàng)。

ps:本章內(nèi)容需要大量隊(duì)列基礎(chǔ)知識(shí),沒有看過第四章節(jié)隊(duì)列的同學(xué),建議先閱讀下隊(duì)列章節(jié)。

1、整體架構(gòu)

首先我們來看一下 AQS 的整體架構(gòu)圖,如下:

圖片描述

 這個(gè)圖總結(jié)了 AQS 整體架構(gòu)的組成,和部分場(chǎng)景的動(dòng)態(tài)流向,圖中兩個(gè)點(diǎn)說明一下,方便大家觀看。

  • AQS 中隊(duì)列只有兩個(gè):同步隊(duì)列 + 條件隊(duì)列,底層數(shù)據(jù)結(jié)構(gòu)兩者都是鏈表;
  • 圖中有四種顏色的線代表四種不同的場(chǎng)景,1、2、3 序號(hào)代表看的順序。

 AQS 本身就是一套鎖的框架,它定義了獲得鎖和釋放鎖的代碼結(jié)構(gòu),所以如果要新建鎖,只要繼承 AQS,并實(shí)現(xiàn)相應(yīng)方法即可。

接下來我們一起來看下這個(gè)圖中各個(gè)細(xì)節(jié)點(diǎn)。

1.1、類注釋

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

  1. 提供了一種框架,自定義了先進(jìn)先出的同步隊(duì)列,讓獲取不到鎖的線程能進(jìn)入同步隊(duì)列中排隊(duì);
  2. 同步器有個(gè)狀態(tài)字段,我們可以通過狀態(tài)字段來判斷能否得到鎖,此時(shí)設(shè)計(jì)的關(guān)鍵在于依賴安全的 atomic value 來表示狀態(tài)(雖然注釋是這個(gè)意思,但實(shí)際上是通過把狀態(tài)聲明為 volatile,在鎖里面修改狀態(tài)值來保證線程安全的);
  3. 子類可以通過給狀態(tài) CAS 賦值來決定能否拿到鎖,可以定義那些狀態(tài)可以獲得鎖,哪些狀態(tài)表示獲取不到鎖(比如定義狀態(tài)值是 0 可以獲得鎖,狀態(tài)值是 1 就獲取不到鎖);
  4. 子類可以新建非 public 的內(nèi)部類,用內(nèi)部類來繼承 AQS,從而實(shí)現(xiàn)鎖的功能;
  5. AQS 提供了排它模式和共享模式兩種鎖模式。排它模式下:只有一個(gè)線程可以獲得鎖,共享模式可以讓多個(gè)線程獲得鎖,子類 ReadWriteLock 實(shí)現(xiàn)了兩種模式;
  6. 內(nèi)部類 ConditionObject 可以被用作 Condition,我們通過 new ConditionObject () 即可得到條件隊(duì)列;
  7. AQS 實(shí)現(xiàn)了鎖、排隊(duì)、鎖隊(duì)列等框架,至于如何獲得鎖、釋放鎖的代碼并沒有實(shí)現(xiàn),比如 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 這些方法,AQS 中默認(rèn)拋 UnsupportedOperationException 異常,都是需要子類去實(shí)現(xiàn)的;
  8. AQS 繼承 AbstractOwnableSynchronizer 是為了方便跟蹤獲得鎖的線程,可以幫助監(jiān)控和診斷工具識(shí)別是哪些線程持有了鎖;
  9. AQS 同步隊(duì)列和條件隊(duì)列,獲取不到鎖的節(jié)點(diǎn)在入隊(duì)時(shí)是先進(jìn)先出,但被喚醒時(shí),可能并不會(huì)按照先進(jìn)先出的順序執(zhí)行。

AQS 的注釋還有很多很多,以上 9 點(diǎn)是挑選出來稍微比較重要的注釋總結(jié)。

1.2、類定義

AQS 類定義代碼如下:

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

可以看出兩點(diǎn):

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

繼承了 AbstractOwnableSynchronizer,AbstractOwnableSynchronizer 的作用就是為了知道當(dāng)前是那個(gè)線程獲得了鎖,方便監(jiān)控用的,

代碼如下:

1.3、基本屬性

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

1.3.1、簡單屬性

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

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

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

1.3.2 、同步隊(duì)列屬性

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

所以同步隊(duì)列的主要作用阻塞獲取不到鎖的線程,并在適當(dāng)時(shí)機(jī)釋放這些線程。

同步隊(duì)列底層數(shù)據(jù)結(jié)構(gòu)是個(gè)雙向鏈表,我們從源碼中可以看到鏈表的頭尾,如下:

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

源碼中的 Node 是同步隊(duì)列中的元素,但 Node 被同步隊(duì)列和條件隊(duì)列公用,所以我們?cè)谡f完條件隊(duì)列之后再說 Node。

1.3.3、條件隊(duì)列的屬性

首先我們介紹下條件隊(duì)列:條件隊(duì)列和同步隊(duì)列的功能一樣,管理獲取不到鎖的線程,底層數(shù)據(jù)結(jié)構(gòu)也是鏈表隊(duì)列,但條件隊(duì)列不直接和鎖打交道,但常常和鎖配合使用,是一定的場(chǎng)景下,對(duì)鎖功能的一種補(bǔ)充。

條件隊(duì)列的屬性如下:

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

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

ConditionObject 是實(shí)現(xiàn) Condition 接口的,Condition 接口相當(dāng)于 Object 的各種監(jiān)控方法,比如 Object#wait ()、Object#notify、Object#notifyAll 這些方法,我們可以先這么理解,后面會(huì)細(xì)說。

1.3.4、Node

Node 非常重要,即是同步隊(duì)列的節(jié)點(diǎn),又是條件隊(duì)列的節(jié)點(diǎn),在入隊(duì)的時(shí)候,我們用 Node 把線程包裝一下,然后把 Node 放入兩個(gè)隊(duì)列中,我們看下 Node 的數(shù)據(jù)結(jié)構(gòu),如下:

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

從 Node 的結(jié)構(gòu)中,我們需要重點(diǎn)關(guān)注 waitStatus 字段,Node 的很多操作都是圍繞著 waitStatus 字段進(jìn)行的。

Node 的 pre、next 屬性是同步隊(duì)列中的鏈表前后指向字段,nextWaiter 是條件隊(duì)列中下一個(gè)節(jié)點(diǎn)的指向字段,但在同步隊(duì)列中,nextWaiter 只是一個(gè)標(biāo)識(shí)符,表示當(dāng)前節(jié)點(diǎn)是共享還是排它模式。

1.3.5、共享鎖和排它鎖的區(qū)別

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

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

1.4、Condition

剛才我們看條件隊(duì)列 ConditionObject 時(shí),發(fā)現(xiàn)其是實(shí)現(xiàn) Condition 接口的,現(xiàn)在我們一起來看下 Condition 接口,其類注釋上是這么寫的:

  1. 當(dāng) lock 代替 synchronized 來加鎖時(shí),Condition 就可以用來代替 Object 中相應(yīng)的監(jiān)控方法了,比如 Object#wait ()、Object#notify、Object#notifyAll 這些方法;
  2. 提供了一種線程協(xié)作方式:一個(gè)線程被暫停執(zhí)行,直到被其它線程喚醒;
  3. Condition 實(shí)例是綁定在鎖上的,通過 Lock#newCondition 方法可以產(chǎn)生該實(shí)例;
  4. 除了特殊說明外,任意空值作為方法的入?yún)?,都?huì)拋出空指針;
  5. Condition 提供了明確的語義和行為,這點(diǎn)和 Object 監(jiān)控方法不同。

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

假設(shè)我們有一個(gè)有界邊界的隊(duì)列,支持 put 和 take 方法,需要滿足:

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

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

1、2 中線程阻塞都會(huì)到條件隊(duì)列中去阻塞。

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

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

void await() throws InterruptedException;

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

當(dāng)以下四種情況發(fā)生時(shí),條件隊(duì)列中的線程將被喚醒

  • 有線程使用了 signal 方法,正好喚醒了條件隊(duì)列中的當(dāng)前線程;
  • 有線程使用了 signalAll 方法;
  • 其它線程打斷了當(dāng)前線程,并且當(dāng)前線程支持被打斷;
  • 被虛假喚醒 (即使沒有滿足以上 3 個(gè)條件,wait 也是可能被偶爾喚醒,虛假喚醒定義可以參考: https://en.wikipedia.org/wiki/Spurious_wakeup)。

被喚醒時(shí),有一點(diǎn)需要注意的是:線程從條件隊(duì)列中蘇醒時(shí),必須重新獲得鎖,才能真正被喚醒,這個(gè)我們?cè)谡f源碼的時(shí)候,也會(huì)強(qiáng)調(diào)這個(gè)。

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

// 返回的 long 值表示剩余的給定等待時(shí)間,如果返回的時(shí)間小于等于 0 ,說明等待時(shí)間過了
// 選擇納秒是為了避免計(jì)算剩余等待時(shí)間時(shí)的截?cái)嗾`差
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 雖然入?yún)⒖梢允侨我鈫挝坏臅r(shí)間,但底層仍然轉(zhuǎn)化成納秒
boolean await(long time, TimeUnit unit) throws InterruptedException;

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

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

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

2、同步器的狀態(tài)

在同步器中,我們有兩個(gè)狀態(tài),一個(gè)叫做 state,一個(gè)叫做 waitStatus,兩者是完全不同的概念:

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

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

這兩個(gè)狀態(tài)我們需要牢記,不要混淆了。

3、獲取鎖

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

Lock 一般是 AQS 的子類,lock 方法根據(jù)情況一般會(huì)選擇調(diào)用 AQS 的 acquire 或 tryAcquire 方法。

acquire 方法 AQS 已經(jīng)實(shí)現(xiàn)了,tryAcquire 方法是等待子類去實(shí)現(xiàn),acquire 方法制定了獲取鎖的框架,先嘗試使用 tryAcquire 方法獲取鎖,獲取不到時(shí),再入同步隊(duì)列中等待鎖。tryAcquire 方法 AQS 中直接拋出一個(gè)異常,表明需要子類去實(shí)現(xiàn),子類可以根據(jù)同步器的 state 狀態(tài)來決定是否能夠獲得鎖,接下來我們?cè)敿?xì)看下 acquire 的源碼解析。

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

3.1、acquire 排它鎖

// 排它模式下,嘗試獲得鎖
public final void acquire(int arg) {
    // tryAcquire 方法是需要實(shí)現(xiàn)類去實(shí)現(xiàn)的,實(shí)現(xiàn)思路一般都是 cas 給 state 賦值來決定是否能獲得鎖
    if (!tryAcquire(arg) &&
        // addWaiter 入?yún)⒋硎桥潘J?
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

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

嘗試執(zhí)行一次 tryAcquire,如果成功直接返回,失敗走 2;線程嘗試進(jìn)入同步隊(duì)列,首先調(diào)用 addWaiter 方法,把當(dāng)前線程放到同步隊(duì)列的隊(duì)尾;接著調(diào)用 acquireQueued 方法,兩個(gè)作用,1:阻塞當(dāng)前節(jié)點(diǎn),2:節(jié)點(diǎn)被喚醒時(shí),使其能夠獲得鎖;如果 2、3 失敗了,打斷線程。

3.1.1、addWaiter

代碼很少,每個(gè)方法都是關(guān)鍵,接下來我們先來看下 addWaiter 的源碼實(shí)現(xiàn):

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

如果之前學(xué)習(xí)過隊(duì)列的同學(xué),對(duì)這個(gè)方法應(yīng)該感覺毫不吃力,就是把新的節(jié)點(diǎn)追加到同步隊(duì)列的隊(duì)尾。

其中有一點(diǎn)值得我們學(xué)習(xí)的地方,是在 addWaiter 方法中,并沒有進(jìn)入方法后立馬就自旋,而是先嘗試一次追加到隊(duì)尾,如果失敗才自旋,因?yàn)榇蟛糠植僮骺赡芤淮尉蜁?huì)成功,這種思路在我們寫自旋的時(shí)候可以借鑒。

3.1.2、acquireQueued

下一步就是要阻塞當(dāng)前線程了,是 acquireQueued 方法來實(shí)現(xiàn)的,我們來看下源碼實(shí)現(xiàn):

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

此方法的注釋還是很清楚的,我們接著看下此方法的核心:shouldParkAfterFailedAcquire,這個(gè)方法的主要目的就是把前一個(gè)節(jié)點(diǎn)的狀態(tài)置為 SIGNAL,只要前一個(gè)節(jié)點(diǎn)的狀態(tài)是 SIGNAL,當(dāng)前節(jié)點(diǎn)就可以阻塞了(parkAndCheckInterrupt 就是使節(jié)點(diǎn)阻塞的方法),

源碼如下:

// 當(dāng)前線程可以安心阻塞的標(biāo)準(zhǔn),就是前一個(gè)節(jié)點(diǎn)線程狀態(tài)是 SIGNAL 了。
// 入?yún)?pred 是前一個(gè)節(jié)點(diǎn),node 是當(dāng)前節(jié)點(diǎn)。
// 關(guān)鍵操作:
// 1:確認(rèn)前一個(gè)節(jié)點(diǎn)是否有效,無效的話,一直往前找到狀態(tài)不是取消的節(jié)點(diǎn)。
// 2: 把前一個(gè)節(jié)點(diǎn)狀態(tài)置為 SIGNAL。
// 1、2 兩步操作,有可能一次就成功,有可能需要外部循環(huán)多次才能成功(外面是個(gè)無限的 for 循環(huán)),但最后一定是可以成功的
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 如果前一個(gè)節(jié)點(diǎn) waitStatus 狀態(tài)已經(jīng)是 SIGNAL 了,直接返回,不需要在自旋了
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    // 如果當(dāng)前節(jié)點(diǎn)狀態(tài)已經(jīng)被取消了。
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        // 找到前一個(gè)狀態(tài)不是取消的節(jié)點(diǎn),因?yàn)榘旬?dāng)前 node 掛在有效節(jié)點(diǎn)身上
        // 因?yàn)楣?jié)點(diǎn)狀態(tài)是取消的話,是無效的,是不能作為 node 的前置節(jié)點(diǎn)的,所以必須找到 node 的有效節(jié)點(diǎn)才行
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    // 否則直接把節(jié)點(diǎn)狀態(tài)置 為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 整個(gè)過程非常長,代碼也非常多,但注釋很清楚,可以一行一行仔細(xì)看看代碼。

總結(jié)一下,acquire 方法大致分為三步:

  • 使用 tryAcquire 方法嘗試獲得鎖,獲得鎖直接返回,獲取不到鎖的走 2;
  • 把當(dāng)前線程組裝成節(jié)點(diǎn)(Node),追加到同步隊(duì)列的尾部(addWaiter);
  • 自旋,使同步隊(duì)列中當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)狀態(tài)為 signal 后,然后阻塞自己。

整體的代碼結(jié)構(gòu)比較清晰,一些需要注意的點(diǎn),都用注釋表明了,強(qiáng)烈建議閱讀下源碼。

3.2、acquireShared 獲取共享鎖

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

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

圖片描述

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

圖片描述

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

// 主要做兩件事情
// 1:把當(dāng)前節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn)
// 2:看看后續(xù)節(jié)點(diǎn)有無正在等待,并且也是共享模式的,有的話喚醒這些節(jié)點(diǎn)
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    // 當(dāng)前節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn)
    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 表示已經(jīng)有節(jié)點(diǎn)獲得共享鎖了
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        //共享模式,還喚醒頭節(jié)點(diǎn)的后置節(jié)點(diǎn)
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
// 釋放后置共享節(jié)點(diǎn)
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;
        // 還沒有到隊(duì)尾,此時(shí)隊(duì)列中至少有兩個(gè)節(jié)點(diǎn)
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 如果隊(duì)列狀態(tài)是 SIGNAL ,說明后續(xù)節(jié)點(diǎn)都需要喚醒
            if (ws == Node.SIGNAL) {
                // CAS 保證只有一個(gè)節(jié)點(diǎn)可以運(yùn)行喚醒的操作
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 進(jìn)行喚醒操作
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 第一種情況,頭節(jié)點(diǎn)沒有發(fā)生移動(dòng),結(jié)束。
        // 第二種情況,因?yàn)榇朔椒梢员粌商幷{(diào)用,一次是獲得鎖的地方,一處是釋放鎖的地方,
        // 加上共享鎖的特性就是可以多個(gè)線程獲得鎖,也可以釋放鎖,這就導(dǎo)致頭節(jié)點(diǎn)可能會(huì)發(fā)生變化,
        // 如果頭節(jié)點(diǎn)發(fā)生了變化,就繼續(xù)循環(huán),一直循環(huán)到頭節(jié)點(diǎn)不變化時(shí),結(jié)束循環(huán)。
        if (h == head)                   // loop if head changed
            break;
    }
}

這個(gè)就是共享鎖獨(dú)特的地方,當(dāng)一個(gè)線程獲得鎖后,它就會(huì)去喚醒排在它后面的其它節(jié)點(diǎn),讓其它節(jié)點(diǎn)也能夠獲得鎖。

4、總結(jié)

AQS 的內(nèi)容實(shí)在太多了,這只是 AQS 的上篇,但內(nèi)容長度已經(jīng)超過了我們平時(shí)章節(jié)的三倍了,所以不得不分節(jié),下一章仍然是 AQS,主要講解鎖的釋放和條件隊(duì)列兩大部分。

以上就是java同步器AQS架構(gòu)AbstractQueuedSynchronizer原理解析的詳細(xì)內(nèi)容,更多關(guān)于java同步器AbstractQueuedSynchronizer的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring Validator接口校驗(yàn)與全局異常處理器

    Spring Validator接口校驗(yàn)與全局異常處理器

    這篇文章主要介紹了Spring Validator接口校驗(yàn)與全局異常處理器,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • 帶你重新認(rèn)識(shí)MyBatis的foreach

    帶你重新認(rèn)識(shí)MyBatis的foreach

    這篇文章主要介紹了重新認(rèn)識(shí)MyBatis的foreach,本文提出了一種簡化<foreach>寫法的設(shè)想,更重要的是通過解決空集時(shí)生成的SQL語法問題,更深刻地理解MyBatis的foreach的生成機(jī)制,需要的朋友可以參考下
    2022-11-11
  • Java 使用Socket正確讀取數(shù)據(jù)姿勢(shì)

    Java 使用Socket正確讀取數(shù)據(jù)姿勢(shì)

    這篇文章主要介紹了Java 使用Socket正確讀取數(shù)據(jù)姿勢(shì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • SpringBoot下載文件的正確解法方式

    SpringBoot下載文件的正確解法方式

    這篇文章主要給大家介紹了關(guān)于SpringBoot下載文件的正確解法方式,SpringBoot是一款流行的框架,用于開發(fā)Web應(yīng)用程序,在使用SpringBoot構(gòu)建Web應(yīng)用程序時(shí),可能需要實(shí)現(xiàn)文件下載的功能,需要的朋友可以參考下
    2023-08-08
  • Java后臺(tái)如何處理日期參數(shù)格式

    Java后臺(tái)如何處理日期參數(shù)格式

    這篇文章主要介紹了Java后臺(tái)如何處理日期參數(shù)格式問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Spring?Validation實(shí)現(xiàn)數(shù)據(jù)校驗(yàn)的示例

    Spring?Validation實(shí)現(xiàn)數(shù)據(jù)校驗(yàn)的示例

    Spring?Validation其實(shí)就是對(duì)Hibernate?Validator進(jìn)一步的封裝,方便在Spring中使用,這篇文章主要介紹了Spring?Validation實(shí)現(xiàn)數(shù)據(jù)校驗(yàn)的示例,需要的朋友可以參考下
    2023-03-03
  • spring boot項(xiàng)目如何采用war在tomcat容器中運(yùn)行

    spring boot項(xiàng)目如何采用war在tomcat容器中運(yùn)行

    這篇文章主要介紹了spring boot項(xiàng)目如何采用war在tomcat容器中運(yùn)行呢,主要講述將SpringBoot打成war包并放入tomcat中運(yùn)行的方法分享,需要的朋友可以參考下
    2022-11-11
  • Mybatis輸入輸出映射及動(dòng)態(tài)SQL Review

    Mybatis輸入輸出映射及動(dòng)態(tài)SQL Review

    這篇文章主要介紹了Mybatis輸入輸出映射及動(dòng)態(tài)SQL Review,需要的朋友可以參考下
    2017-02-02
  • java中金額元轉(zhuǎn)萬元工具類的實(shí)例

    java中金額元轉(zhuǎn)萬元工具類的實(shí)例

    這篇文章主要介紹了java中金額元轉(zhuǎn)萬元工具類的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Java利用異常中斷當(dāng)前任務(wù)的技巧分享

    Java利用異常中斷當(dāng)前任務(wù)的技巧分享

    在日常開發(fā)中,我們經(jīng)常遇到調(diào)用別人的代碼來完成某個(gè)任務(wù),但是當(dāng)代碼比較耗時(shí)的時(shí)候,沒法從外部終止該任務(wù),所以本文為大家介紹了如何利用異常中斷當(dāng)前任務(wù),需要的可以參考下
    2023-08-08

最新評(píng)論