JUC核心-AQS(AbstractQueuedSynchronizer)簡析

AQS是什麼?

  • AQS 全稱是 AbstractQueuedSynchronizer, 它提供一種依賴於FIFO等待隊列的構建鎖和同步器的框架

  • CAS是什麼?

CAS(Compare And Swap),即比較並交換。是解決多線程並行情況下使用鎖造成性能損耗的一種機制,CAS操作包含三個操作數——內存位置(V)、預期原值(A)和新值(B)。如果內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。否則,處理器不做任何操作。無論哪種情況,它都會在CAS指令之前返回該位置的值。CAS有效地說明了“我認爲位置V應該包含值A;如果包含該值,則將B放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。

底層實現

整體結構

  • AQS 的結構大概可總結爲以下 3 部分:

    • 用 volatile 修飾的整數類型的 state 狀態,用於表示同步狀態,提供 getState 和 setState, compareAndSetState來操作同步狀態;
    • 提供了一個 FIFO 等待隊列,實現線程間的競爭和等待,這是 AQS 的核心;其中, 鏈表頭Head和鏈表尾Tail也有volatile修飾。
    • AQS 內部提供了各種基於 CAS 原子操作方法,如 compareAndSetState 方法,並且提供了鎖操作的acquire和release方法。
  • 提供兩種鎖的默認實現方式:

    • 獨佔鎖(Exclusive)
    • 共享鎖(Shared)
  • tryAcquire*, tryRelease* 都是需要實現類自己去實現的方法, 如果不實現的話,是會拋出異常UnsupportedOperationException的

  • 用到的設計模式-模板模式, 在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但調用將以抽象類中定義的方式進行。這種類型的設計模式屬於行爲型模式。

獨佔鎖

acquire獲取獨佔鎖

  • 僞代碼實現
 while (!tryAcquire(arg)) {
    <em>enqueue thread if it is not already queued</em>;
    <em>possibly block current thread</em>;
 }
  • 代碼實現
    • 先嚐試獲取鎖,獲取成功則成功
    • 嘗試失敗,則把當前線程包裝成爲一個節點,然後等待獲取的機會
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • NOTE這裏有必要說明一下,就是當一個節點成爲head節點的時候,他不一定會是下一個獲取鎖的節點,從上面代碼也可以看出來,所以獲取鎖的線程都會先嚐試獲取鎖一次,這樣有可能等待隊列的頭節點也可能獲取鎖失敗。

release釋放獨佔鎖

  • 僞代碼實現
  if (tryRelease(arg))
     <em>unblock the first queued thread</em>;
  • 代碼實現
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

共享鎖

acquireShared獲取共享鎖

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

releaseShared釋放共享鎖

  • 代碼實現
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

FIFO隊列

隊列模型

這個在AQS的註釋說明裏邊,Doug Lea已經說的很明確了,還做了圖解

  • 這個等待隊列是CLH(Craig, Landin, and Hagersten)隊列的一個變種,這種隊列常被用作自旋鎖(這個概念就不展開了)
  • 作用:用於阻塞同步器
  • 結構
         +------+  prev +-----+       +-----+
 head    |      | <---- |     | <---- |     |  tail
         +------+       +-----+       +-----+
  • 入隊:插入隊尾
  • 出隊:直接設置head即可

節點

  • 節點其實是把想要獲取鎖的線程包裝了一番
//mode分exclusive和shared兩種模式
new Node(Thread.currentThread(), mode);
  • 節點狀態
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate. 
 * 這個狀態只在共享鎖的模式下有效,這個傳播的用處在哪兒呢?
 * 舉例說明:讀寫鎖,寫讀操作和寫寫操作互斥,讀讀之間不互斥;當調用acquireShared獲取讀
 * 鎖時,會檢查後續節點是否是獲取讀鎖,如果是,則同樣釋放;
 */
static final int PROPAGATE = -3;

常見面試

  • 談一下AQS吧 @可以從定義入手,然後講
    • 不同鎖狀態的更改的實現方式
    • FIFO隊列的實現方式
    • 核心技術CAS+volatile
  • CAS是什麼?見上面的筆記
  • 爲什麼你說AQS的底層是CAS+volatile?
    • 表示鎖狀態的變量state,以及FIFO隊列的頭,尾,節點的狀態都是volatile修飾的
    • 在設置state,隊列的頭,尾,狀態的時候都有用到CAS技術
  • JUC包裏的同步組件主要實現了AQS的哪些主要方法 ?
    • tryAcquire, tryRelease
    • tryAcquireShared, tryReleaseShared
    • isHeldExclusively
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章