1. 程式人生 > >AQS詳解(面試)

AQS詳解(面試)

AQS原理
AQS:AbstractQuenedSynchronizer抽象的佇列式同步器。是除了java自帶的synchronized關鍵字之外的鎖機制。

AQS的核心思想是,如果被請求的共享資源空閒,則將當前請求資源的執行緒設定為有效的工作執行緒,並將共享資源設定為鎖定狀態,如果被請求的共享資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH佇列鎖實現的,即將暫時獲取不到鎖的執行緒加入到佇列中。
CLH(Craig,Landin,and Hagersten)佇列是一個虛擬的雙向佇列,虛擬的雙向佇列即不存在佇列例項,僅存在節點之間的關聯關係。
AQS是將每一條請求共享資源的執行緒封裝成一個CLH鎖佇列的一個結點(Node),來實現鎖的分配。

用大白話來說,AQS就是基於CLH佇列,用volatile修飾共享變數state,執行緒通過CAS去改變狀態符,成功則獲取鎖成功,失敗則進入等待佇列,等待被喚醒。

**注意:AQS是自旋鎖:**在等待喚醒的時候,經常會使用自旋(while(!cas()))的方式,不停地嘗試獲取鎖,直到被其他執行緒獲取成功

實現了AQS的鎖有:自旋鎖、互斥鎖、讀鎖寫鎖、條件產量、訊號量、柵欄都是AQS的衍生物
AQS實現的具體方式如下:
在這裡插入圖片描述
如圖示,AQS維護了一個volatile int state和一個FIFO執行緒等待佇列,多執行緒爭用資源被阻塞的時候就會進入這個佇列。state就是共享資源,其訪問方式有如下三種:
getState();setState();compareAndSetState();

AQS 定義了兩種資源共享方式:
1.Exclusive:獨佔,只有一個執行緒能執行,如ReentrantLock
2.Share:共享,多個執行緒可以同時執行,如Semaphore、CountDownLatch

不同的自定義的同步器爭用共享資源的方式也不同。
自定義同步器在實現的時候只需要實現共享資源state的獲取和釋放方式即可,至於具體執行緒等待佇列的維護,AQS已經在頂層實現好了。自定義同步器實現的時候主要實現下面幾種方法:
isHeldExclusively():該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。
tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。
tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。

ReentrantLock為例,(可重入獨佔式鎖):state初始化為0,表示未鎖定狀態,A執行緒lock()時,會呼叫tryAcquire()獨佔鎖並將state+1.之後其他執行緒再想tryAcquire的時候就會失敗,直到A執行緒unlock()到state=0為止,其他執行緒才有機會獲取該鎖。A釋放鎖之前,自己也是可以重複獲取此鎖(state累加),這就是可重入的概念。
注意:獲取多少次鎖就要釋放多少次鎖,保證state是能回到零態的。

以CountDownLatch為例,任務分N個子執行緒去執行,state就初始化 為N,N個執行緒並行執行,每個執行緒執行完之後countDown()一次,state就會CAS減一。當N子執行緒全部執行完畢,state=0,會unpark()主呼叫執行緒,主呼叫執行緒就會從await()函式返回,繼續之後的動作。

一般來說,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支援自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。
 在acquire() acquireShared()兩種方式下,執行緒在等待佇列中都是忽略中斷的,acquireInterruptibly()/acquireSharedInterruptibly()是支援響應中斷的。

AQS的簡單應用
Mutex:不可重入互斥鎖,鎖資源(state)只有兩種狀態:0:未被鎖定;1:鎖定。

class Mutex implements Lock, java.io.Serializable {
    // 自定義同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 判斷是否鎖定狀態
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 嘗試獲取資源,立即返回。成功則返回true,否則false。
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // 這裡限定只能為1個量
            if (compareAndSetState(0, 1)) {//state為0才設定為1,不可重入!
                setExclusiveOwnerThread(Thread.currentThread());//設定為當前執行緒獨佔資源
                return true;
            }
            return false;
        }

        // 嘗試釋放資源,立即返回。成功則為true,否則false。
        protected boolean tryRelease(int releases) {
            assert releases == 1; // 限定為1個量
            if (getState() == 0)//既然來釋放,那肯定就是已佔有狀態了。只是為了保險,多層判斷!
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);//釋放資源,放棄佔有狀態
            return true;
        }
    }

    // 真正同步類的實現都依賴繼承於AQS的自定義同步器!
    private final Sync sync = new Sync();

    //lock<-->acquire。兩者語義一樣:獲取資源,即便等待,直到成功才返回。
    public void lock() {
        sync.acquire(1);
    }

    //tryLock<-->tryAcquire。兩者語義一樣:嘗試獲取資源,要求立即返回。成功則為true,失敗則為false。
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    //unlock<-->release。兩者語文一樣:釋放資源。
    public void unlock() {
        sync.release(1);
    }

    //鎖是否佔有狀態
    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

同步類在實現時一般都將自定義同步器(sync)定義為內部類,供自己使用;而同步類自己(Mutex)則實現某個介面,對外服務。
部分轉自:http://www.cnblogs.com/waterystone/p/4920797.html