tomcat堆栈中10大常见线程详解
Tomcat作为一个服务器来讲,必然运行着很多的线程,而每一个线程究竟是干什么的,这个需要非常的清楚,无论是打印断点,还是通过jstack进行线程栈分析,这都是必须要掌握的技能。 本文带你基于Tomcat7,8,9的版本,识别Tomcat堆栈中的线程。1、main线程main线程是tomcat的主要线程,其主要作用是通过启动包来对容器进行点火:main线程一路启动了Catalina,Standar
Tomcat作为一个服务器来讲,必然运行着很多的线程,而每一个线程究竟是干什么的,这个需要非常的清楚,无论是打印断点,还是通过jstack进行线程栈分析,这都是必须要掌握的技能。 本文带你基于Tomcat7,8,9的版本,识别Tomcat堆栈中的线程。
1、main线程
main线程是tomcat的主要线程,其主要作用是通过启动包来对容器进行点火:
main线程一路启动了Catalina,StandardServer[8005],StandardService[Catalina],StandardEngine[Catalina]
engine内部组件都是异步启动,engine这层才开始继承ContainerBase,engine会调用父类的startInternal()方法,里面由startStopExecutor线程提交FutureTask任务,异步启动子组件StandardHost,
StandardEngine[Catalina].StandardHost[localhost]
main->Catalina->StandardServer->StandardService->StandardEngine->StandardHost,黑体开始都是异步启动。
->启动Connector
main的作用就是把容器组件拉起来,然后阻塞在8005端口,等待关闭。
2、localhost-startStop线程
Tomcat容器被点火起来后,并不是傻傻的按照次序一步一步的启动,而是在engine组件中开始用该线程提交任务,按照层级进行异步启动,对于每一层级的组件都是采用startStop线程进行启动,我们观察一下idea中的线程堆栈就可以发现:启动异步,部署也是异步
这个startstop线程实际代码调用就是采用的JDK自带线程池来做的,启动位置就是ContainerBase的组件父类的startInternal():
因为从Engine开始往下的容器组件都是继承这个ContainerBase,所以相当于每一个组件启动的时候,除了对自身的状态进行设置,都会启动startChild线程启动自己的孩子组件。
而这个线程仅仅就是在启动时,当组件启动完成后,那么该线程就退出了,生命周期仅仅限于此。
3、AsyncFileHandlerWriter线程
日志输出线程:
顾名思义,该线程是用于异步文件处理的,它的作用是在Tomcat级别构架出一个输出框架,然后不同的日志系统都可以对接这个框架,因为日志对于服务器来说,是非常重要的功能。
如下,就是juli的配置:
该线程主要的作用是通过一个LinkedBlockingDeque来与log系统对接,该线程启动的时候就有了,全生命周期。
4、ContainerBackgroundProcessor线程
Tomcat在启动之后,不能说是死水一潭,很多时候可能会对Tomcat后端的容器组件做一些变化,例如部署一个应用,相当于你就需要在对应的Standardhost加上一个StandardContext,也有可能在热部署开关开启的时候,对资源进行增删等操作,这样应用可能会重新reload。
也有可能在生产模式下,对class进行重新替换等等,这个时候就需要在Tomcat级别中有一个线程能实时扫描Tomcat容器的变化,这个就是ContainerbackgroundProcessor线程了:
(本地源码StandardContext类的5212行启动)
我们可以看到这个代码,也就是在ContainerBase中:
这个线程是一个递归调用,也就是说,每一个容器组件其实都有一个backgroundProcessor,而整个Tomcat就点起一个线程开启扫描,扫完儿子,再扫孙子(实际上来说,主要还是用于StandardContext这一级,可以看到StandardContext这一级:
我们可以看到,每一次backgroundProcessor,都会对该应用进行一次全方位的扫描,这个时候,当你开启了热部署的开关,一旦class和资源发生变化,立刻就会reload。
tomcat9中已经被Catalina-Utility线程替代。
5、acceptor线程
Connector(实际是在AbstractProtocol类中)初始化和启动之时,启动了Endpoint,Endpoint就会启动poller线程和Acceptor线程。Acceptor底层就是ServerSocket.accept()。返回Socket之后丢给NioChannel处理,之后通道和poller线程绑定。
acceptor->poller->exec
无论是NIO还是BIO通道,都会有Acceptor线程,该线程就是进行socket接收的,它不会继续处理,如果是NIO的,无论是新接收的包还是继续发送的包,直接就会交给Poller,而BIO模式,Acceptor线程直接把活就给工作线程了:
如果不配置,Acceptor线程默认开始就开启1个,后期再随着压力增大而增长:
上述启动代码在AbstractNioEndpoint的startAcceptorThreads方法中。
6、ClientPoller线程
NIO和APR模式下的Tomcat前端,都会有Poller线程:
对于Poller线程实际就是继续接着Acceptor进行处理,展开Selector,然后遍历key,将后续的任务转交给工作线程(exec线程),起到的是一个缓冲,转接,和NIO事件遍历的作用,具体代码体现如下(NioEndpoint类):
上述的代码在NioEndpoint的startInternal中,默认开始开启2个Poller线程,后期再随着压力增大增长,可以在Connector中进行配置。
7、exe线程(默认10个)
也就是SocketProcessor线程,我们可以看到,上述几个线程都是定义在NioEndpoint内部线程类。NIO模式下,Poller线程将解析好的socket交给SocketProcessor处理,它主要是http协议分析,攒出Response和Request,然后调用Tomcat后端的容器:
该线程的重要性不言而喻,Tomcat主要的时间都耗在这个线程上,所以我们可以看到Tomcat里面有很多的优化,配置,都是基于这个线程的,尽可能让这个线程减少阻塞,减少线程切换,甚至少创建,多利用。
下面就是NIO模式下创建的工作线程:
实际上也是JDK的线程池,只不过基于Tomcat的不同环境参数,对JDK线程池进行了定制化而已,本质上还是JDK的线程池。
8、NioBlockingSelector.BlockPoller(默认2个)
Nio方式的Servlet阻塞输入输出检测线程。实际就是在Endpoint初始化的时候启动selectorPool,selectorPool再启动selector,selector内部启动BlokerPoller线程。
该线程在前面的NioBlockingPool中讲得很清楚了,其NIO通道的Servlet输入和输出最终都是通过NioBlockingPool来完成的,而NioBlockingPool又根据Tomcat的场景可以分成阻塞或者是非阻塞的,对于阻塞来讲,为了等待网络发出,需要启动一个线程实时监测网络socketChannel是否可以发出包,而如果不这么做的话,就需要使用一个while空转,这样会让工作线程一直损耗。
只要是阻塞模式,并且在Tomcat启动的时候,添加了—D参数 org.apache.tomcat.util.net.NioSelectorShared 的话,那么就会启动这个线程。
大体上启动顺序如下:
//bind方法在初始化就完成了
Endpoint.bind(){
//selector池子启动
selectorPool.open(){
//池子里面selector再启动
blockingSelector.open(getSharedSelector()){
//重点这句
poller = new BlockPoller();
poller.selector = sharedSelector;
poller.setDaemon(true);
poller.setName("NioBlockingSelector.BlockPoller-"+ (threadCounter.getAndIncrement()));
//这里启动
poller.start();
}
}
}
9、AsyncTimeout线程
该线程为tomcat7及之后的版本才出现的,注释其实很清楚,该线程就是检测异步request请求时,触发超时,并将该请求再转发到工作线程池处理(也就是Endpoint处理)。
AsyncTimeout线程也是定义在AbstractProtocol内部的,在start()中启动。AbstractProtocol是个极其重要的类,他持有Endpoint和ConnectionHandler这两个tomcat前端非常重要的类
10、其他线程(例如ajp相关线程)
ajp工作线程处理的是ajp协议的相关请求,这个请求主要是用于http apache服务器和tomcat之间的数据交换,该数据交换用的就是ajp协议,和exec工作线程差不多,默认也是启动10个,端口号是8009。优化时如果没有用到http apache的话就可以把这个协议关掉。
Tomcat本身还有很多其它的线程,远远不止这些,例如如果开启了sendfile,那么对sendfile就是开启一个线程来进行操作,这种功能的线程开启还有很多。
Tomcat作为一款优秀的服务器,不可能就只有1个线程,而是多个线程之间相互配合完成功能,而且很多功能尽量异步处理,尽可能的减少线程切换。所以线程并不是越多越好,因此线程的控制也尤为关键。
以上线程的源码分析详细讲解请查看以下文档
更多推荐
所有评论(0)