一文看透Tomcat 是如何用CountDownLatch停止容器的?

背景

Tomcat 源码中多处用了java.util.concurrent 包中的类,用以处理多线程环境下的流程控制。近日分析了下NioEndpoint 源码,本文将以此类为背景,膜拜下 Java 大神们使用 CountDownLatch 并发控制的手法,其实也就是简单的实际应用,算不上高深。

类图框架

NIO tailored thread pool, providing the following services:
Socket acceptor thread:Acceptor
Socket poller thread:Poller
Worker threads pool:Executor

以上是该类的注释,结合源码我们知道 NioEndpoint 就是一个定制线程池,管理了三种线程:Acceptor、Poller、Worker。
(百来的一张很清晰的结构图如下:)
这里写图片描述

初始化

NioEndpoint 类维护了一个 stopLatch 的变量,其类型就是 CountDownLatch。它根据 Poller 线程的个数进行初始化的,源码如下:

public void bind() throws Exception {
	....
	if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
        if (pollerThreadCount <= 0) {
            //minimum one poller thread
            pollerThreadCount = 1;
        }
        stopLatch = new CountDownLatch(pollerThreadCount);
	....
}

NioEndpont 类初始化时指定了 Poller 和 Accetpor 线程数,而且从上面代码的注释信息来看 acceptorThreadCount 的固定 是 1,即 Tomcat 的 NIO 并不支持多个 Accepor 线程,此外也没有可以修改该属性的途径。

stopLatch控制流程

stopLatch,顾名思义,是控制 Tomcat 的组件停止时使用的锁,利用 CountDownLatch ,主线程等待一组线程到达某个状态后,才进行后面的处理。NioEndpoint 的 stopInternal() 方法的流程如下:

public void stopInternal() {
        releaseConnectionLatch();
        if (!paused) {
            pause();
        }
        if (running) {
            running = false;
            unlockAccept();
            for (int i=0; pollers!=null && i<pollers.length; i++) {
                if (pollers[i]==null) continue;
                pollers[i].destroy();
                pollers[i] = null;
            }
            try {
                stopLatch.await(selectorTimeout + 100, TimeUnit.MILLISECONDS);
            } catch (InterruptedException ignore) {
            }
            shutdownExecutor();
            eventCache.clear();
            nioChannels.clear();
            processorCache.clear();
        }
 }

该方法将导致所有的处理线程都停止工作,其流程为:

  1. 首先,通知Poller线程停止工作,调用其 destroy,设置 Poller 的 close 标识为 true。
  2. 其次,设置 running 为 false,通知 Acceptor 线程终止 run 方法的循环处理。
  3. 第三,当前线程 stopLatch.await ,等待所有的 Poller 线程的 run方法结束。Poller 的 run 方法最后一句是 stopLatch.countDown(),当 stopInternal 的 await 方法被唤醒时,说明所有的Poller 线程都结束了。
  4. 第四,此处调用 await 操作的超时时间设置为 selectorTimeout,这个值也是Poller处理时的阻塞时间,也就是说:如果Poller的在轮询过程中调用了selector.select(selectorTimeout);的话,最多等待这么长时间,就能保证所有的Poller都及时结束了。

此处,之所以不用考虑 Acceptor 的结束问题,是因为 Acceptor 线程只有一个,而且它没有阻塞处理,所以一旦 running 标识为 false,它就会立即结束。

所有的处理线程都结束之后,shutdownExecutor() 操作会关闭工作线程池的调度器,至此,所有的线程都被关闭了。

启示录

开发中,如何需要自定义线程池框架,就可以参照这个流程对线程池资源进行关闭,用 JUC 包中的并发工具类,比自己写同步计数器方便多了!Tomcat 教我们的这一招,你学会了吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值