java并發之AbstractQueuedSynchronizer

引言

可能我們平常很少聽說它,就算是在并發里也很少用到它,但是我們所用的Condition(條件)、ReentrantLock(可重入鎖)、ReentrantReadWriteLock(讀寫鎖)就是根據它所構建的,它叫做隊列同步器,是構建其他鎖和同步組件的基礎框架。

同步器的主要使用方法是繼承,子類通過繼承同步器并實現它的抽象方法來管理同步狀態,在抽象方法的實現過程中免不了對同步狀態進行更改,就需要使用同步器提供的3個最主要的方法

 protected final int getState() ; //獲取同步狀態
 protected final void setState(int newState) ;//設置同步狀態
 protected final boolean compareAndSetState(int expect, int update) ;//原子性設置同步狀態(使用CAS)

AQS本身是沒有實現任何同步接口的,它僅僅是定義了若干同步狀態的獲取和釋放來供自定義同步組件使用,它可以支持獨占式的獲取同步狀態(只能有一個線程使用,其他都得等待),也支持共享式的獲取同步狀態(如讀寫鎖中的讀鎖)

同步器的設計是采用的模板方法設計模式,我們只需要重寫指定的方法,隨后將同步器組合在自定義同步組件中,并調用模板方法提供的模板方法即可。

源碼分析

AQS里面可以重寫的主要方法如下(需要我們自己實現邏輯):

        protected boolean tryAcquire(int arg) ; //獨占式的獲取鎖

        protected boolean tryRelease(int arg) ;//獨占式的釋放鎖
       
        protected int tryAcquireShared(int arg) ;//共享式的獲取鎖

        protected boolean tryReleaseShared(int arg) ; //共享式的釋放鎖
      
        protected boolean isHeldExclusively() ; //判斷當前線程是否是在獨占模式下被線程占用,一般表示是否被當前線程所占用
       

AQS提供的主要模板方法如下(我們不能重寫):

   public final void acquire(int arg) ; //獨占式獲取鎖

   public final void acquireInterruptibly(int arg); //與acquire(int arg)一樣,但響應中斷

   public final boolean tryAcquireNanos(int arg, long nanosTimeout);//在acquireInterruptibly(int arg基礎上加入了超時機制,在規定時間內沒有獲取到鎖返回false
獲取到了返回true

   public final boolean release(int arg) ;//獨占式的釋放同步狀態

   public final void acquireShared(int arg) ; //共享式的獲取鎖

   public final void acquireSharedInterruptibly(int arg);  //在acquireShared(int arg)加入了對中斷的響應

  public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)  //在acquireSharedInterruptibly(int arg)基礎上加入超時機制

  public final boolean releaseShared(int arg) ; //共享式的釋放鎖

這上面的所有方法都加上了final,表明了它們不可繼承,這就是模板方法設計模式

下面我們來看一下它的源碼

  • 首先是獨占式同步鎖的獲取
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

這是獨占式獲取鎖的入口

具體流程如下:
 1、用tryacquire(arg)去獲取同步狀態(需要我們自己實現)
 2、獲取不到把線程封裝Node節點(addwaiter),在加入等待隊列中
  • tryAcquire(int arg)源碼
protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

源碼就是簡單地拋出異常,所以需要我們自己去實現

  • addWaiter 源碼
 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //嘗試加入到隊尾,因為這可能有多個線程競爭,采用compareAndSetTail(CAS操作)
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
      //如果表頭為空又或者節點沒加入到隊尾
        enq(node);
        return node;
    }

先構造成Node節點

  • Node是其內部類,主要構造如下
static final class Node {
  volatile int waitStatus;
  volatile Node prev;
  volatile Node next;
  volatile Thread thread;
  Node nextWaiter;
}
  • prev:前驅節點;

  • next:后繼節點;

  • thread:進入隊列的當前線程

  • nextWaiter:存儲condition隊列中的后繼節點。

    • waitStatus:節點狀態,主要有這幾種狀態:
      1、 CANCELLED:當前線程被取消;
      2、SIGNAL:當前節點的后繼節點需要運行;
      3、 CONDITION:當前節點在等待condition
      4、PROPAGATE:當前場景下后續的acquireShared可以執行;

隊列的基本結構如下:

3994601-525f0d33e1512800.png
  • enq源碼
/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
     //死循環(自旋)地插入到隊尾
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //通過compareAndSetTail保證節點能被線程安全添加
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

設置尾節點的過程

3994601-f9f4486a56dc72f4.png

加入到同步隊列中之后,就執行 acquireQueued

  • acquireQueued源碼

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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);
        }
    }

節點進入同步隊列后,就開始自旋,每個節點都在觀察自己是否滿足條件,當“條件”滿足,就可以獲取同步狀態,然后從自旋地過程中退出

這里有兩點要說明:
1、頭節點是獲取到同步狀態的點,而頭節點的線程釋放同步狀態后,會喚醒其后續節點,所以每個節點都在判斷自己的前驅節點是否是頭節點,是之后看能不能獲取到同步狀態,只有兩者都滿足時,才能退出
2、維護同步隊列的是fifo原則(先進先出),整個過程如圖所示:

3994601-032edf539ec5d54b.png

總結一下獨占式獲取鎖的流程:

3994601-59833f358ae1cf04.png

當前線程獲取到同步狀態后,就需要釋放同步狀態,使后續節點能夠獲取,主要是通過release(int arg) 實現的

  • release(int arg) 源碼
 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:判斷能否釋放同步狀態
unparkSuccessor(h):喚醒后續節點

上面講了獨占式獲取獲取鎖,下面講講共享式獲取

共享式和獨占式最大的區別是同一時刻能否有多個線程同時獲取到同步狀態,如圖所示

3994601-af326de0d86eb2e9.png

共享式獲取的入口是acquireShared 方法

  • acquireShared 源碼
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

tryAcquireShared方法是需要我們自己實現的,返回的是int值,當返回值大于0時,表示能獲取到同步狀態

  • doAcquireShared源碼
 private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

先把線程加入到同步隊列中,然后死循環,判斷前驅節點是否是頭節點,然后獲取同步狀態,當兩者都滿足是就退出循環

與獨占式獲取同步狀態一樣,共享式獲取也是需要釋放同步狀態的,AQS提供releaseShared(int arg)方法可以釋放同步狀態。

  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

1、調用tryReleaseShared方法釋放狀態;
2、 調用doReleaseShared方法喚醒后繼節點;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容