前言
之前分析的ReentrantLock
和CountDownLatch
都是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應用到自己的代碼中。