Tomcat线程池简单介绍

前言

tomcat的线程池扩展了jdk的executor,而且队列用的是自己的task queue,因此其策略与jdk的有所不同。

本篇将讨论一下tomcat线程池和jdk线程池的不同之处,以及tomcat为什么要重写jdk线程池的方法。

一、Tomcat的请求处理过程

image-20230330220238498

一个客户端请求到达Tomcat之后的处理流程如上图所示:

  • 当Tomcat启动后,Connector的接收器Acceptor会监听是否有客户端连接。
  • 一旦监听到客户端连接,则将连接交给线程池Executor,开始执行请求响应任务。
  • Http11Processor负责从客户端连接中读取Http报文并进行解析,解析后的报文封装成Request对象。
  • Maper根据Http协议请求的URL值和Host属性匹配由哪个Host、哪个Context和哪个Wrapper容器来处理请求。
  • CoyoteAdaptor负责将Connector组件和Engine容器连接起来,将Request对象和Response对象传递到Engine容器中。
  • Engine容器的请求处理管道开始工作,管道里包括若干Valve,每个Valve都负责一些处理逻辑。
  • Engine容器的请求处理管道工作完成后,再依次交给Host容器的处理管道、Context容器的处理管道和Wrapper容器的处理管道。最后将结果输出到客户端。

二、tomcat在哪里用到了线程池

image-20230330220255785

说明

  • LimitLatch用来限流,可以控制最大连接个数
  • acceptor负责接收新的socket连接
  • poller负责监听socket channel是否有可读的IO事件
  • 一旦有可读的IO事件被监听到,就封装一个任务对象socketProcessor,提交给executor线程池处理
  • executor线程池中的工作线程最终负责处理请求

三、Tomcat线程池和JUC线程池流程

JDK线程池策略:

  1. 当线程池中线程数量小于corePoolSize,每来一个任务,就会创建一个线程执行这个任务。
  2. 当前线程池线程数量大于等于corePoolSize,则每来一个任务。会尝试将其添加到任务缓存队列中,若是添加成功,则该任务会等待线程将其取出去执行;若添加失败(一般来说任务缓存队列已满),则会尝试创建新的线程执行。
  3. 当前线程池线程数量等于maximumPoolSize,则会采取任务拒绝策略进行处理。

tomcat线程池策略:

  1. 当前线程数小于corePoolSize,则去创建工作线程;
  2. 当前线程数大于corePoolSize,但小于maximumPoolSize,则去创建工作线程;
  3. 当前线程数大于maximumPoolSize,则将任务放入到阻塞队列中,当阻塞队列满了之后,则调用拒绝策略丢弃任务;

Tomcat线程池对JUC线程池的增强

Tomcat是重写了JDK线程池(即改动的是少量的源码)实现的功能的增强。

主要流程还是JDK线程池流程,即先开启corePoolSize线程,然后在queue,最后在开启maximumPoolSize线程。

Tomcat重点改造的是queue的offer()。即在向queue放入任务时,若发现未达到最大线程数,那么offer()返回false,即放入队列失败。此时,便继续开启maximumPoolSize线程。

那么Tomcat为什么要重新改造JDK线程池呢?

四、Tomcat线程池和JUC线程池的区别

使用线程池的任务有两种

  • IO密集型任务(如调用接口、查询数据库);
  • CPU密集型任务;

场景

  • JDK线程池:当线程数达到corePoolSize后,任务首先被放到queue。发挥CPU多核的并行优势,减少多个线程导致的上下文切换。

    适合的场景是:CPU密集型任务

  • Tomcat线程池:当大量请求达到时,接收的请求数量大于核心线程池的corePoolSize时,会继续创建worker线程去处理请求。而后续请求量变少时,只会销毁maximumPoolSize线程数。

    适合的场景是:IO密集型任务

JDK的线程池ThreadPoolExecutor主要目的解决的便是CPU密集型任务的并发处理。但是Tomcat若使用原生的JDK线程池,一旦接收的请求数量大于线程池的核心线程数,这些请求就会被放到队列中,等待核心线程处理。这样会降低请求的总体处理速度,所以Tomcat并没有使用JDK原生线程池的策略。

Tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同

  • 如果总线程数达到 maximumPoolSize
    • 这时不会立刻抛 RejectedExecutionException 异常
    • 而是再次尝试将任务放入队列,如果还失败,才抛出 RejectedExecutionException 异常

Tomcat线程池简单介绍
http://example.com/2023/03/30/Tomcat线程池简单介绍/
作者
程序员小魏
发布于
2023年3月30日
许可协议