Tomcat中LimitLatch实现原理
LimitLatch中的抽象同步队列AQS
AQS在java中是锁的底层支持,不论是独占锁还是共享锁内部实现都使用了AQS,那为什么LimitLatch也能用AQS实现呢?
- 锁的本质实际上是对资源的管理,这里的资源可以是一个变量,一块内存,一个数据表。独占锁和共享锁是对资源访问的不同限制,独占锁只允许一个线程使用资源,而共享锁则可以多个线程使用资源。
- 在LimitLatch中,资源就是一个连接,count变量就是对连接的量化,对连接进行限制,就是对count变量进行限制,因此可以使用AQS来实现对连接的控制。
在看LimitLatch源码之前,先对AQS进行了解,这是理解LimitLatch的基础。
AQS概述
当多个线程同时获取同一个锁的时候,没有获取到锁的线程需要排队等待,等锁被释放的时候,队列中的某个线程被唤醒,然后获取锁。AQS中就维护了这样一个同步队列(CLH队列)。
AQS类图结构
- AQS是一个先进先出的双向队列,内部通过head和tail记录队首和队尾元素。
- 其节点数据结构为Node,其中的 thread变量用来存放进入AQS队列里面的线程;Node节点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS 队列的,EXCLUSIVE 用来标记线程是获取独占资源时被挂起后放入AQS队列的;waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点); prev记录当前节点的前驱节点,next记录当前节点的后继节点。
- 对于AQS来说,线程同步的关键是对状态值state进行操作。根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。
- 在独占方式下获取和释放资源使用的方法为:
void acquire(int arg)
void acquireInterruptibly(int arg)
boolean release(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//tryAcquire没有具体的实现,需要子类自行实现
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//没有具体的实现,需要子类自行实现
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
(1)当一个线程调用acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.EXCLUSIVE的Node节点后插入到AQS 阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
(2)当一个线程调用release(int arg)方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。
AQS没有提供具体的tryAcquire与tryRelease实现方法,子类要根据具体的使用场景自己实现具体的方法。
- 在共享方式下获取和释放资源的方法为:
void acquireShared(int arg)
void acquireSharedInterruptibly(int arg)
boolean releaseShared(int arg)。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//tryAcquireShared没有具体的实现,需要子类自行实现
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//没有具体的实现,需要子类自行实现
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
(1)当线程调用acquireShared(int arg)获取共享资源时,会首先使用tryAcquireShared尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.SHARED的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己。
(2)当一个线程调用releaseShared(int arg)时会尝试使用tryReleaseShared 操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark ( thread)激活AQS 队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS 队列并被挂起。
LimitLatch使用AQS实现连接的控制
在了解AQS的基础上再来看LimitLatch就会十分清晰了,LimitLatch中使用共享方式来获取资源。
下面来看看LimitLatch的源码
public class LimitLatch {
//Sync继承AbstractQueuedSynchronizer用于实现连接控制器的逻辑,对资源请求进行控制
//这里的资源实际上就是LimitLatch中的count,类似于原本的state
//当count小于limit,则申请成功count+1;申请失败时则当前线程插入AQS队列被挂起直到有资源被释放
private class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1L;
Sync() {
}
//重写tryAcquireShared方法用于实现资源申请
@Override
protected int tryAcquireShared(int ignored) {
long newCount = count.incrementAndGet();
if (!released && newCount > limit) {
count.decrementAndGet();
return -1;
} else {
// 申请成功count自增1,这里的count是原子类
return 1;
}
}
//释放资源count自减1
@Override
protected boolean tryReleaseShared(int arg) {
count.decrementAndGet();
return true;
}
}
private final Sync sync;
//声明为原子类是因为多线程环境下对count进行操作
private final AtomicLong count;
private volatile long limit;
private volatile boolean released = false;
//初始化count为0,limit在AbstractEndpoint中默认值为1024*8
public LimitLatch(long limit) {
this.limit = limit;
this.count = new AtomicLong(0);
this.sync = new Sync();
}
public long getCount() {
return count.get();
}
public long getLimit() {
return limit;
}
public void setLimit(long limit) {
this.limit = limit;
}
//请求操作,实际上调用了AQS的acquireSharedInterruptibly方法,acquireSharedInterruptibly内部调用了sync覆写的tryAcquireShared方法
public void countUpOrAwait() throws InterruptedException {
if (log.isDebugEnabled()) {
log.debug("Counting up["+Thread.currentThread().getName()+"] latch="+getCount());
}
sync.acquireSharedInterruptibly(1);
}
//释放资源操作,调用了releaseShared方法释放资源count自减1
public long countDown() {
sync.releaseShared(0);
long result = getCount();
if (log.isDebugEnabled()) {
log.debug("Counting down["+Thread.currentThread().getName()+"] latch="+result);
}
return result;
}
public boolean releaseAll() {
released = true;
return sync.releaseShared(0);
}
public void reset() {
this.count.set(0);
released = false;
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
}
从以上代码可以看出
- countUpOrAwait()方法用于对资源的申请,实际上调用了AQS的acquireSharedInterruptibly方法,acquireSharedInterruptibly内部调用了sync覆写的tryAcquireShared方法,实现逻辑就是判断count是否小于limit,申请成功则count自增1;申请失败时则当前线程插入AQS队列被挂起直到有资源被释放。
- countDown()释放资源操作,调用了releaseShared方法释放资源count自减1。
LimitLatch在Endpoint中的使用
AbstarctEndPoint中定义了initializeConnectionLatch()方法,来实现LimitLatch的初始化。
protected LimitLatch initializeConnectionLatch() {
if (maxConnections==-1) {
return null;
}
if (connectionLimitLatch==null) {
//getMaxConnections()获取MaxConnections默认值是1024*8
connectionLimitLatch = new LimitLatch(getMaxConnections());
}
return connectionLimitLatch;
}
AbstarctEndPoint中定义了startInternal()方法是一个抽象方法,需要不同子类根据需要进行实现,该方法用于开启一个EndPoint。以下是NioEndPoint的实现,其中调用了initializeConnectionLatch()方法开启一个连接控制器。
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
//初始化LimitLatch
initializeConnectionLatch();
//开启一个pollor线程
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
startAcceptorThread();
}
}