前言
之前分析的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应用到自己的代码中。