深入理解Java併發類——AQS

什麼是AQS

AbstractQueuedSynchronizer,是Java併發包中,實現各種同步結構和部分其他組成單元的基礎,如線程池中的Worker

爲什麼需要AQS

Doug Lea曾經介紹過AQS的設計初衷。從原理上,一種同步結構往往是可以利用其他的結構實現的。但是,對某種同步結構的傾向,會導致複雜的實現邏輯,所以他選擇將基礎的同步相關操作抽象在AbstractQueuedSynchronizer中,利用AQS爲我們構建同步結構提供範本。

AQS的內部數據和方法

  1. 一個volatile的整數成員表徵狀態,同時提供了setState和getState方法。

    private volatile int state;
  2. 一個先入先出(FIFO)的等待線程隊列,以實現多線程間競爭和等待,這是AQS機制的核心之一

  3. 各種基於CAS的基礎操作方法,以及各種期望具體同步結構去實現的acquire/release方法。

如何利用AQS實現同步結構

利用AQS實現一個同步結構,至少要實現兩個基本類型的方法,分別是:

  1. acquire操作,獲取資源的獨佔權.
  2. 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講》楊曉峯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章