LimitLatch

前言

之前分析的ReentrantLockCountDownLatch都是JDK中對AQS的利用,分別實現了獨佔鎖和共享鎖。

接着我們再來看一個LimitLatch,來學習下我們可以怎麼將AQS應用到我們自己的程序中。

LimitLatch這個類是我在看Tomcat源碼的時候看到的,當時並沒有太在意,也是這次複習JUC包的時候突然想起的,所以就想着來看下具體實現。Tomcat使用LimitLatch類實現了對請求連接數的控制。

LimitLatch使用場景

當有一個連接進來的時候就會調用

AbstractEndpoint#countUpOrAwaitConnectio()

    protected void countUpOrAwaitConnection() throws InterruptedException {
        if (maxConnections==-1) return;
        LimitLatch latch = connectionLimitLatch;
        if (latch!=null) latch.countUpOrAwait();
    }

當一個請求進入到Tomcat的時候,就會調用latch.countUpOrAwaitConnection(),如果當前的鏈接數已經超過了最大限制,那麼當前線程就會被阻塞,如果沒有,那麼當前線程就繼續執行下去。

當連接關閉或者取消的時候,就會調用countDown()方法來釋放鏈接。在AbstractEndpoint#countDownConnection()方法中被調用。

 protected long countDownConnection() {
        if (maxConnections==-1) return -1;
        LimitLatch latch = connectionLimitLatch;
        if (latch!=null) {
            long result = latch.countDown();
            if (result<0) {
                getLog().warn(sm.getString("endpoint.warn.incorrectConnectionCount"));
            }
            return result;
        } else return -1;
    }

LimitLatch源碼分析

  • 先看構造方法
   public LimitLatch(long limit) {
        this.limit = limit;
        this.count = new AtomicLong(0);
        this.sync = new Sync();
    }

在使用LimitLatch的時候,我們需要把最大連接數limit通過構造方法傳入到LimitLatch中。

在LimitLatch除了將最大鏈接數limit賦值給limit屬性外。還會初始化一個Sync對象,Sync依然是AQS的一個子類。另外將count屬性置爲0。

  • 接着看countUpOrAwait()
public void countUpOrAwait() throws InterruptedException {
        if (log.isDebugEnabled()) {
            log.debug("Counting up["+Thread.currentThread().getName()+"] latch="+getCount());
        }
        sync.acquireSharedInterruptibly(1);
    }

和CountDownLatch一樣,都是直接調用的AQS中的final方法acquireSharedInterruptibly(),來嘗試獲取共享鎖。但是對於何時獲取共享鎖的邏輯實現是不一樣的。

  @Override
        protected int tryAcquireShared(int ignored) {
    	
            long newCount = count.incrementAndGet();
            //如果count大於最大的限制並且released標識爲false(默認爲false,在調用relaseAll的時候會置爲true)
            if (!released && newCount > limit) {
                // Limit exceeded
                //原子性的減1,還原
                count.decrementAndGet();
                //這裏返回-1 返回-1的時候就會將當前線程放入CLH隊列中等待被喚醒
                return -1;
            } else {
                //返回1 則當前線程不需要被掛起
                return 1;
            }
        }

1.count是一個原子類,將count原子性的加一,然後將newCount與最大連接數比較,如果不超過最大連接數,那麼成功獲取共享鎖,當前線程繼續執行。

2.如果超過最大連接數,而且released屬性爲false(默認爲false),那麼就需要還原之前原子性的加一,然後返回-1,當前線程將進入自旋獲取鎖的過程,自旋過程中沒有成功獲取鎖會被阻塞。

很簡單的代碼就實現了我們想要的功能:當前的鏈接數已經超過了最大限制,那麼當前線程就會被阻塞,如果沒有,那麼當前線程就繼續執行下去。

  • 最後看countDown()方法
    public long countDown() {
        sync.releaseShared(0);
        long result = getCount();
        if (log.isDebugEnabled()) {
            log.debug("Counting down["+Thread.currentThread().getName()+"] latch="+result);
    }
        return result;
    }

 

直接調用了AQS的final方法releaseShared()。我們看LimitLatch$Sync中重寫的tryReleaseShared()

是如何定義何時釋放共享鎖的邏輯

    protected boolean tryReleaseShared(int arg) {
            count.decrementAndGet();
            return true;
        }

實現很簡單,就是將count原子性的減一,然後始終返回true。也就意味着調用LimitLatch.countDown()方法時,AQS.doReleaseShared()方法是一定會調用的。這個方法之前分析過,當頭節點不是初始化的節點或者不爲null時,就會去喚醒CLH隊列中掛起的線程。而我們知道,當我們調用countUpOrAwait的時候,只有count超過limit限制的時候,纔會構造一個Node,掛起當前線程。所以當沒有超過limit的時候CLH隊列中是不會有任何元素的。完美的實現了我們的需求。

總結

不得不再次感嘆Doug Lea大師對AQS的實現真的太棒了,我們只需要編寫很簡潔的一些代碼就能實現我們想要的自定義鎖。希望以後也能將AQS應用到自己的代碼中。

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