Java學習——AQS解析

最近在學習JUC,發現裏面大部分的併發組件都是基於AQS,因此研讀了一下源碼,在此做個筆記。

AQS(AbstractQueuedSynchronizer)翻譯爲抽象隊列同步器,是除Synchronized關鍵字外java自帶的鎖機制。

AQS使用一個int類型變量state來表示線程要競爭的資源,state的值即爲可獲取的資源數,當一個線程嘗試獲取鎖時,會使用CAS方式去嘗試改變state的值,如果改變成功,即獲取了鎖。CAS的實現是使用了Unsafe類底下的一系列compareAndSet*方法,這些方法調用了JAVA的native方法,可以認爲是系統底層支持的原子操作。

 private final boolean compareAndSetHead(Node update);
 private final boolean compareAndSetTail(Node expect, Node update);
 private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) ;
 private static final boolean compareAndSetNext(Node node,Node expect,Node update)

當線程嘗試獲取鎖時,AQS會將線程封裝爲一個節點,放入到一個等待隊列中。這個等待隊列是一個FIFO的雙向隊列,處於等待隊列中的線程獲取鎖失敗後,會調用LockSupport類中的park方法,使等待的線程放棄調度資格,進入time_waiting的狀態。所以AQS的狀態通常如下圖所示。總是位於header的線程持有鎖,處於running狀態,剩餘的等待線程處於time-waitting狀態(此處以獨佔模式爲例),當header的線程執行完,釋放鎖時,將優先喚醒header節點的下一節點的線程進行鎖的申請。
在這裏插入圖片描述
如下代碼是等待隊列中的線程嘗試獲取鎖的處理過程,可以看出,等待線程以自旋的方式,循環嘗試獲取鎖。在header節點線程釋放鎖時,會優先喚醒header節點的下一節點,但是被喚醒的線程並不一定能獲取鎖,還是得進行獲取鎖的嘗試,如果失敗,會進入time-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);
        }
    }

在文章開頭,筆者說過,juc中有很多併發組件是利用AQS實現的,這裏,我以ReenTrantLock爲例,介紹一下java的併發組件是怎樣以AQS爲基礎設計實現鎖。
ReenTrantLock是使用AQS實現的一種獨佔鎖,可以是非公平鎖或者公平鎖,我們以實現比較簡單的的非公平鎖爲例,鎖使用最基本的兩個接口實現爲Lock和unLock,分別表示申請鎖和釋放鎖。
在這裏插入圖片描述
從流程圖可以看出,AQS實現了一整套的線程阻塞等待、線程被喚醒時的組織機制,而ReentrantLock爲了實現非公平鎖,只需要重寫tryAcquire和tryRelease方法,這兩個方法分別表示非阻塞方式的嘗試獲取鎖和非阻塞方式的釋放鎖。

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

ReenTrantLock的tryAcquire方法會調用nonfairTryAcquire方法,實現了非阻塞獲取鎖的處理邏輯,而且也實現了可重入機制,當獲得鎖的線程再次申請鎖時,只是簡單的對state值進行修改。

//in AQS
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	//釋放header的下一節點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
//rewrite in ReentrantLock
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
       

ReenTrantLock的unlock方法會調用AQS的release方法,release方法定義了鎖釋放的一系列操作,包括下一節點的喚醒。這實際上是一種模板方法的設計模式,而ReenTrantLock重寫的tryRelease方法只是簡單的實現了鎖的釋放。

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