看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 。
(百来的一张很清晰的结构图如下:)
看Tomcat如何用CountDownLatch停止容器

文章插图
 
初始化NioEndpoint 类维护了一个 stopLatch 的变量,其类型就是 CountDownLatch 。它根据 Poller 线程的个数进行初始化的,源码如下:
public void bind() throws Exception { .... if (acceptorThreadCount == 0) {// FIXME: Doesn't seem to work that well with multiple accept threadsacceptorThreadCount = 1;}if (pollerThreadCount <= 0) {//minimum one poller threadpollerThreadCount = 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 教我们的这一招,你学会了吗?

【看Tomcat如何用CountDownLatch停止容器】


    推荐阅读