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,顾名思义,是控制 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();
}
}
该方法将导致所有的处理线程都停止工作,其流程为:
此处,之所以不用考虑 Acceptor 的结束问题,是因为 Acceptor 线程只有一个,而且它没有阻塞处理,所以一旦 running 标识为 false,它就会立即结束。
所有的处理线程都结束之后,shutdownExecutor() 操作会关闭工作线程池的调度器,至此,所有的线程都被关闭了。
开发中,如何需要自定义线程池框架,就可以参照这个流程对线程池资源进行关闭,用 JUC 包中的并发工具类,比自己写同步计数器方便多了!Tomcat 教我们的这一招,你学会了吗?