Tomcat中LimitLatch组件原理

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的方式分为独占方式和共享方式。
  1. 在独占方式下获取和释放资源使用的方法为:

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实现方法,子类要根据具体的使用场景自己实现具体的方法。

  1. 在共享方式下获取和释放资源的方法为:

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();
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值