canal原始碼之BooleanMutex(基於AQS中共享鎖實現)

阿阿sa 發表於 2021-09-24

在看canal原始碼時發現一個有趣的鎖實現--BooleanMutex

這個鎖在canal裡面多處用到,比如系統初始化/授權控制,沒許可權時阻塞等待,有許可權時所有執行緒都可以快速通過,還有canal在使用叢集模式做高可用的時候它用來控制只有active節點可做操作

先看它的核心基於AQS的鎖實現:

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

        /**
         * 實現AQS的介面,釋放共享鎖的判斷
         */
        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中共享鎖的判斷可持有方法tryAcquireShared和判斷可釋放鎖的方法tryReleaseShared

也就是說,它是重寫AQS中共享鎖的方法來實現的,有意思的是它不像普通AQS實現的共享鎖,比如Semaphore這種有許可證個數,也就是同時能被幾個執行緒持有是固定的,而它理論上只要開啟了開關就允許任意執行緒通過

下面看下它是怎麼實現的:

首先它定義了兩個int型別屬性:

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

這兩個屬性代表了AQS中state的值,也就是說這把鎖中的state值始終為1或者2,看下下面這兩個方法

    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;
                }
            }
        }

 顯而易見,這兩個方法都自旋的CAS改變state的值,那這兩個方法在哪裡呼叫呢?

  public void set(Boolean mutex) {
        if (mutex) {
            sync.innerSetTrue();
        } else {
            sync.innerSetFalse();
        }
    }

這個方法封裝了改變state值的方法,其實它就相當於我們常用鎖的加鎖和釋放鎖方法,原因在於它獲取共享鎖的判斷:

protected int tryAcquireShared(int state) {
    return isTrue(getState()) ? 1 : -1;
}   
private boolean isTrue(int state) {
    return (state & TRUE) != 0;
}

由於state值始終為1或0,當state值為0時isTrue返回false,tryAcquireShared返回-1,AQS在共享鎖的獲取鎖判斷中如果tryAcquireShared<0代表共享鎖許可證已經頒發完,後續申請鎖的執行緒需要進入FIFO佇列等待

相反如果state值為1,tryAcquireShared>0就直接獲取鎖物件

所以,當set(false)的時候,自旋的CAS把state值改成0,執行緒需要等待,相反set(true)就能獲取鎖成功