1、Tomcat线程模型概述
- tomcat介绍
- connector服务实现接收客户端请求,然后将请求转向Servlet容器
- 下面详细介绍Connector部分的线程模型
- Tomcat线程模型
- 这里以tomcat-embed-core:9.0.46为例
- tcp三次握手后:
- 作用:接受客户端请求,建立tcp连接
- 逻辑:内核层:服务端收到客户端的 ACK 后,从半连接队列中取出连接放到全连接队列(Accept queue)中,服务端进入 ESTABLISHED 状态,等待应用层:ServerSocket执行accept()方法
- Tomcat-Acceptor线程
- 作用:执行accept()方法返回socket对象,告知Poller线程。
- 逻辑:从Accept queue取出返回socket,创建一个Poller的OP_REGISTER事件,放入到Poller的SynchronizedQueue
- Tomcat-Poller线程
- 作用:负责监听socket的IO可读写事件
- 逻辑:从Poller的SynchronizedQueue取出事件,并在Selector里注册socket的io可读写事件
- Tomcat-ThreadPoolExecutor工作线程池
- 作用:可读写的socket,交给线程池里的线程处理业务
- 逻辑:字节流解析成http协议对应对象,并处理业务逻辑
2、Tomcat线程模型源码分析
2.1、Springboot主线程(启动Acceptor线程、Poller线程、worker线程池)
2.1.1、初始化NioEndpoint
- 在Connector的startInternal()方法时,开始初始化ProtocalHandler,同时会初始化NioEndpoint(包含了初始化Acceptor线程、Poller线程和Worker线程池)
- 在NioEndpoint类,初始化的时候,会调用startInternal()方法
- 会启动Acceptor线程、Poller线程、初始化worker线程池
2.1.2. 初始化worker线程池Executor
- 在NioEndpoint类,初始化的时候,会调用startInternal()方法,其中就会初始化worker线程池:createExecutor()
- tomcat自己实现的taskQueue,是继承LinkedBlockingQueue,也是无界的,tomcat控制逻辑:在最前置acceptor就控制了总连接数,所以这个队列不会有OOM风险
2.2、Acceptor线程(接收到请求,将socket塞给Poller对象里面的同步队列)
2.3、 Poller线程(从Poller对象里的同步队列取Event)
从队列里取出Event,并且注册到selector
2.4、 Poller线程(处理IO读写事件,交给worker线程池)
- Poller的run()方法一直循环,处理selector的IO读写事件,并交给tomcat-worker线程池处理
- 创建socketProcessor对象,交给Executor线程池处理
2.5、 Worker线程池(处理业务)
2.5.1、Executor.execute(ScoketProcessor)线程池执行任务
- 这里Tomcat的线程池继承jdk线程池,并根据web场景,做了些定制优化
- jdk线程池逻辑:队列满了后,再创建非核心线程
- Tomcat线程池逻辑:先创建非核心线程池,线程数达到最大线程数值,再进队列
- 提交任务会计数:总共提交了多少任务
- 同时还是交给父类ThreadPoolExecutor处理
- tomcat的TaskQueue类的offer()方法的实现:当提交的任务数>线程池已有线程数,并且已有线程数<最大线程数,则不把任务加入队列,直接返回false(外层逻辑走到创建非核心线程数)
2.5.2、worker线程执行具体任务
- Executor.execute(ScoketProcessor)传入的是SocketProcessor类,所以会调用该类的run()方法,执行业务逻辑(基本就是拿socket解析http协议,封装request和response给selevlet用)
- NioEndpoint的内部类SocketProcessor,继承了SocketProcessorBase类,doRun()方法就是执行tomcat业务逻辑入口
- ....执行一系列业务逻辑后
- 写数据给客户端:是在servlet里就做了,例如springmvc的dispatchServlet