AQS 應用 —— 手寫 Semaphore、CountDownLatch

1 AQS 簡介

  • AQS 爲抽象鎖隊列,它內部引入了一個鎖隊列,並實現了獲取鎖隊列失敗後線程的入隊列操作和成功釋放鎖後,喚醒隊列下一個節點線程的出隊列操作
  • AQS 也提供了獲取和釋放鎖的模板,子類只要實現獲取/釋放鎖(tryAcquire/tryRelease)的邏輯,就可以實現一個完成的鎖,如 ReentrantLock、ReentrantReadWriteLock
  • AQS、ReentrantLock、ReentrantReadWriteLock 結構與源碼分析 一文中 分析了AQS 的結構

2 Semaphore 簡介

  • 需求分析:一個旅遊景區,當人數達到額定值時,就不能在放入進去了,出來一個人才能進去一個人
  • 概要設計:AQS 正好可以實現上面的需求
    • 可將 state 變量設爲額定值(state 表示剩餘的可獲取的資源)
    • 當一個線程獲取鎖成功後,將 state - 1。釋放鎖成功後,將 state + 1
    • 當 state < = 0 時,表示沒有資源了,獲取資源的線程將被掛起

2.1 Semaphore 設計

  • 以下即是最簡潔的 Semaphore 所需要的核心代碼,自己實現的僅僅只有 tryAcquireShared()、tryReleaseShared()。其餘的部分都被 AQS 實現了,可見 AQS 的強大。
public class MySemaphore {
    private Sync sync;
    public MySemaphore(int permits) {
        this.sync = new Sync(permits);
    }
    //我覺得不應該響應中斷,因爲被中斷的鎖顯然沒有獲取到鎖,這時就會走 finally 中的 release() 方法來釋放鎖,這樣就可能會拋 error 錯誤了
    public void acquire() {sync.acquireShared(1);}
    public void release() {sync.releaseShared(1);}
    static class Sync extends AbstractQueuedSynchronizer {
        private int max;
        public Sync(int permits) {
            setState(permits);
            max = permits;
        }
        @Override
        protected int tryAcquireShared(int arg) {
            int state;
            for (;;) {
                //當資源不夠的時候就要阻塞當前線程了,否則一直循環CAS,直到成功爲止,因爲只要有資源就不應該被阻塞
                if ((state = getState()) - arg < 0) return -1;
                if (compareAndSetState(state,state-arg)){
                    return 1;
                }
            }
        }
        @Override
        //因爲沒有辦法判斷線程是否持有共享鎖,爲了防止隨意釋放,就直接拋 error 了
        //每次釋放必定成功,因爲每次釋放,都應該喚醒等待隊列中的線程
        protected boolean tryReleaseShared(int arg) {
            for(;;) {
                int state = getState();
                int nextS = state + arg;
                if (nextS > max) {
                    throw new Error("acquire 和  release 的個數不相同!");
                }
                if (compareAndSetState(state,nextS)) {
                    return true;
                }
            }
        }
    }
}

3 CountDownLatch 簡介

  • 需求分析:某場考試,只有所有考生都來了之後,監考老師才能髮捲子(真實情況不可能哦)
  • 概要設計:同樣可以用 AQS 來實現
    • 可將額定值設爲 state 變量(state 的值表示有多少把鎖還沒有被釋放)
    • 某個考試入場,則釋放一把鎖(state - 1),如果 state == 0 時,才喚醒所有等待鎖的線程(監考老師)
    • 並且獲取鎖成功的條件是 state <= 0

3.1 CountDownLatch 設計

  • 同樣最簡潔的 CountDownLatch 所需要的核心代碼,也僅僅實現了 tryAcquireShared() 和 tryReleaseShared() 方法,就將我們需要的功能實現了,可見 AQS 的強大
public class MyCountDownLatch {
    private Sync sync;
    public MyCountDownLatch(int count){sync = new Sync(count);}
    
    public void countDown(){sync.releaseShared(1);}
    //這裏的 await() 方法可以設計成響應中斷模式,因爲 CountDownLatch 釋放鎖的操作是由其他線程調用的
    public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}

    static class Sync extends AbstractQueuedSynchronizer {
        public Sync(int count) {setState(count);}
        //當 state 爲 0 的時候才獲取到,不爲0就被阻塞
        @Override
        protected int tryAcquireShared(int arg) {return getState() <= 0 ? 1 : -1;}
        @Override
        protected boolean tryReleaseShared(int arg) {
            for(;;) {
                int state = getState();
                //如果 state 已經小於等於0了,說明等待線程已經被喚醒了。就不用這個線程來再次喚醒了
                if (state <= 0) return false;
                int nextS = state - arg;
                if (compareAndSetState(state,nextS)) {
                    return nextS <= 0;//這時如果小於等於0,就應該去喚醒等待線程了
                }
            }
        }
    }
}

4 總結

  • 我們只需通過重寫 AQS 中的 獲取鎖、釋放鎖方法,就可以實現各種功能的鎖!
  • 並且 AQS 已經爲我們提供了中斷鎖、tryLock() 的全部代碼。我們僅僅需要實現普通鎖,就可以直接得到另外兩把鎖!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章