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应用到自己的代码中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值