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