java併發神器 AQS(AbstractQueuedSynchronizer)

天愛有情發表於2020-03-21

AbstractQueuedSynchronizer

  • AQS的全稱為(AbstractQueuedSynchronizer),這個類在java.util.concurrent.locks包

  • AQS的核心思想是,如果被請求的共享資源空閒,則將當前請求資源的執行緒設定為有效的工作執行緒,並將共享資源設定為鎖定狀態,如果被請求的共享資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH佇列鎖實現的,即將暫時獲取不到鎖的執行緒加入到佇列中。

  • CLH(Craig,Landin,and Hagersten)佇列是一個虛擬的雙向佇列,虛擬的雙向佇列即不存在佇列例項,僅存在節點之間的關聯關係。

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

  • CAS(Compare and Swap) 科普

    ​ 如字面意思,就是先比較再替換,

    ​ cas方法有三個重要引數 : 待比較的值、預期值、要修改的新值, 如果預期值是待比較的值一致,那麼就把 要修改的值賦值給待比較的值

    ​ 虛擬碼如下:

    Object old; // 待比較的值
    Object new; // 預期值
    Object update;// 要修改的新值
    if(old == new) {
        old = update;
    }
    複製程式碼

    ​ CAS(比較並交換)是CPU指令級的操作,只有一步原子操作,所以非常快。而且CAS避免了請求作業系統來裁定鎖的問題,不用麻煩作業系統,直接在CPU內部就搞定了。

    java中操作cas:

    sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
    Demo old = new Demo();
    long offset = unsafe.objectFieldOffset
                    (Demo.class.getDeclaredField("id"))
        // 如果 old.id 是 1, 那麼修改成 2
    return unsafe.compareAndSwapObject(old, offset, 1, 2);
    複製程式碼
  • AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個執行緒能執行,如ReentrantLock)和Share(共享,多個執行緒可同時執行,如Semaphore/CountDownLatch)

    • 獨佔鎖API

      • isHeldExclusively():該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。

        aqs未實現該方法,需要手動實現,判斷當前執行緒是否已獲得資源,返回boolean

      • tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。

        aqs抽象類未實現該方法, 一般都需要重寫該方法

      • tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。

        ​ aqs抽象類未實現該方法,一般需要重寫該方法來釋放資源

    • 共享鎖API

      • tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。

        ​ aqs抽象類未實現該方法, 一般都需要重寫該方法

      • tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放後允許喚醒後續等待結點返回true,否則返回false。aqs抽象類未實現該方法,一般需要重寫該方法來釋放資源

    • 獨佔api和共享api的區別

      • 當AQS的子類實現獨佔功能時,如 ReentrantLock,資源是否可以被訪問被定義為:只要AQS的state變數不為0,並且持有鎖的執行緒不是當前執行緒,那麼代表資源不可訪問。
      • 當AQS的子類實現共享功能時,如CountDownLatch,資源是否可以被訪問被定義為:只要AQS的state變數不為0,那麼代表資源不可以為訪問。
      • ReentrantReadWriteLock 中讀鎖用的共享鎖, 寫鎖用的獨佔鎖
    • 獨佔鎖例子

      • 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是自旋鎖:**在等待喚醒的時候,經常會使用自旋(while(!cas()))的方式,不停地嘗試獲取鎖,直到被其他執行緒獲取成功

aqs的一個獨佔鎖簡單DEMO

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共享鎖demo:

// 拷貝至 RocketMQ原始碼, 基於boolean的共享鎖
public class BooleanMutex {

    private Sync sync;

    public BooleanMutex() {
        sync = new Sync();
        set(false);
    }

    public BooleanMutex(Boolean mutex) {
        sync = new Sync();
        set(mutex);
    }

    /**
     * 阻塞等待Boolean為true
     *
     * @throws InterruptedException
     */
    public void get() throws InterruptedException {
        sync.innerGet();
    }

    /**
     * 阻塞等待Boolean為true,允許設定超時時間
     *
     * @param timeout
     * @param unit
     * @throws InterruptedException
     * @throws TimeoutException
     */
    public void get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
        sync.innerGet(unit.toNanos(timeout));
    }

    /**
     * 重新設定對應的Boolean mutex
     *
     * @param mutex
     */
    public void set(Boolean mutex) {
        if (mutex) {
            sync.innerSetTrue();
        } else {
            sync.innerSetFalse();
        }
    }

    public boolean state() {
        return sync.innerState();
    }

    /**
     * Synchronization control for BooleanMutex. Uses AQS sync state to
     * represent run status
     */
    private final class Sync extends AbstractQueuedSynchronizer {

        private static final long serialVersionUID = 2559471934544126329L;
        /**
         * State value representing that TRUE
         */
        private static final int TRUE = 1;
        /**
         * State value representing that FALSE
         */
        private static final int FALSE = 2;

        private boolean isTrue(int state) {
            return (state & TRUE) != 0;
        }

        /**
         * 實現AQS的介面,獲取共享鎖的判斷
         */
        @Override
        protected int tryAcquireShared(int state) {
            // 如果為true,直接允許獲取鎖物件
            // 如果為false,進入阻塞佇列,等待被喚醒
            return isTrue(getState()) ? 1 : -1;
        }

        /**
         * 實現AQS的介面,釋放共享鎖的判斷
         */
        @Override
        protected boolean tryReleaseShared(int ignore) {
            // 始終返回true,代表可以release
            return true;
        }

        boolean innerState() {
            return isTrue(getState());
        }

        void innerGet() throws InterruptedException {
            acquireSharedInterruptibly(0);
        }

        void innerGet(long nanosTimeout) throws InterruptedException, TimeoutException {
            if (!tryAcquireSharedNanos(0, nanosTimeout)) throw new TimeoutException();
        }

        void innerSetTrue() {
            for (; ; ) {
                int s = getState();
                if (s == TRUE) {
                    return; // 直接退出
                }
                if (compareAndSetState(s, TRUE)) {// cas更新狀態,避免併發更新true操作
                    releaseShared(0);// 釋放一下鎖物件,喚醒一下阻塞的Thread
                    return;
                }
            }
        }

        void innerSetFalse() {
            for (; ; ) {
                int s = getState();
                if (s == FALSE) {
                    return; // 直接退出
                }
                if (compareAndSetState(s, FALSE)) {// cas更新狀態,避免併發更新false操作
                    return;
                }
            }
        }

    }

}
複製程式碼

總結:

AQS分為獨佔鎖和共享鎖兩種模式:

  • 獨佔鎖

    執行**acquire(int arg)**時,只能有一個執行緒,多個執行緒的話會被加入到阻塞佇列中同步執行

    執行**release()**時喚醒下一個執行緒

    tryAcquire(int arg)返回的是boolean

  • 共享鎖

    執行**acquireShared(int arg)**時,可以多個執行緒進行爭搶

    執行**releaseShared()**時如果成功, 則把佇列中所有的執行緒全部執行

    **releaseShared()**完成後才返回

    tryAcquireShared(int arg)返回的是int型別的值

相關文章