什麼是AQS
AbstractQueuedSynchronizer,是Java併發包中,實現各種同步結構和部分其他組成單元的基礎,如線程池中的Worker。
爲什麼需要AQS
Doug Lea曾經介紹過AQS的設計初衷。從原理上,一種同步結構往往是可以利用其他的結構實現的。但是,對某種同步結構的傾向,會導致複雜的實現邏輯,所以他選擇將基礎的同步相關操作抽象在AbstractQueuedSynchronizer中,利用AQS爲我們構建同步結構提供範本。
AQS的內部數據和方法
一個volatile的整數成員表徵狀態,同時提供了setState和getState方法。
private volatile int state;
一個先入先出(FIFO)的等待線程隊列,以實現多線程間競爭和等待,這是AQS機制的核心之一。
各種基於CAS的基礎操作方法,以及各種期望具體同步結構去實現的acquire/release方法。
如何利用AQS實現同步結構
利用AQS實現一個同步結構,至少要實現兩個基本類型的方法,分別是:
- acquire操作,獲取資源的獨佔權.
- release操作,釋放對某個資源的獨佔。
以ReentrantLock爲例,它內部通過擴展AQS實現了Sync類型,以AQS的state來反映鎖的持有情況。
private final Sync sync;
absract static class Sync extends AbsractQueuedSynchronizer { …}
下面是ReentrantLock對應acquire和release操作,如果是CountDownLatch則可以看作是await()/countDown(),具體實現也有區別。
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
下面是線程試圖獲取鎖的過程,acquire()的實現邏輯是在AQS的內部,調用了tryAcquire和acquireQueued。
tryAcquire是按照特定場景需要開發者去實現的部分,而線程間競爭則是AQS通過Waiter隊列與acquireQueued提供的。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在ReentrantLock中,tryAcquire的邏輯實現在NonfairSync和FairSync中,分別提供了進一步的非公平或公平性方法,而AQS內部tryAcquire僅僅 是個接近未實現的方法(直接拋異常)。
ReentrantLock默認是非公平的,下面是公平性在ReentrantLock構建時如何設定的。
public ReentrantLock() {
sync = new NonfairSync(); // 默認是非公平的
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
以非公平的tryAcquire爲例,其內部實現瞭如何配合狀態與CAS獲取鎖。對比公平版本的tryAcquire,非公平版本在鎖無人佔有時,並不檢查是否有其他等待者,這裏體現了非公平語義。
fnal boolean nonfairTryAcquire(int acquires) {
fnal Thread current = Thread.currentThread();
int c = getState();// 獲取當前AQS內部狀態量
if (c == 0) { // 0表示無人佔有,則直接用CAS修改狀態位,
if (compareAndSetState(0, acquires)) {// 不檢查排隊情況,直接爭搶
setExclusiveOwnerThread(current); //並設置當前線程獨佔鎖
return true;
}
} else if (current == getExclusiveOwnerThread()) { //即使狀態不是0,也可能當前線程是鎖持有者
//因爲這是再入鎖
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
接下來分析acquireQueued,如果前面的tryAcquire失敗,代表着鎖爭搶失敗,進入排隊競爭階段。
當前線程會被包裝成爲一個排他模式的節點(EXCLUSIVE),通過addWaiter方法添加到FIFO隊列中。
如果當前節點的前面是頭節點,則試圖 獲取鎖,一切順利則成爲新的頭節點;否則,有必要則等待。
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {// 循環
final Node p = node.predecessor();// 獲取前一個節點
if (p == head && tryAcquire(arg)) { // 如果前一個節點是頭結點,表示當前節點合適去tryAcquire
setHead(node); // acquire成功,則設置新的頭節點
p.next = null; // 將前面節點對當前節點的引用清空
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node)) // 檢查是否失敗後需要park
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);// 出現異常,取消
if (interrupted)
selfInterrupt();
throw t;
}
}
總結。以上是對ReentrantLock的acquire方法的分析,release方法與之相似。
參考
《Java併發編程實戰》
《Java核心技術36講》楊曉峯