Tomcat线程池原理(上篇:初始化原理)

文章目录

  • 前言
  • 正文
    • 一、从启动脚本开始分析
    • 二、ProtocolHandler 的启动原理
    • 三、AbstractEndPoint 的启动原理
    • 四、创建默认线程池
    • 五、参数配置原理
      • 5.1 常规的参数配置
      • 5.2 自定义线程池
      • 5.3 测试自定义线程

前言

在Java Web的开发过程中,Tomcat常用的web容器。SpringBoot之前,我们用的是单独的 Tomcat,SpringBoot时代,嵌入了Tomcat。

在Jdk中,JUC内有线程框架,以及可以自定义参数配置的 TreadPoolExecutor。Tomcat内也实现了自己的线程池。

所谓线程池,是被用来处理传入的 HTTP 请求的。
当客户端发送请求时,Tomcat 会从线程池中获取一个可用的线程来处理该请求。处理完请求后,线程将返回线程池,并在下一个请求到来时再次被重用。

究其原因,是JUC内的线程池不符合Tomcat的使用场景。

  • Jdk中的线程池,是cpu密集型(也就是偏计算,处理完了可以去队列再取任务)
  • Tomcat的应用场景,却大多是IO密集型的。(也就是要求IO尽量不要阻塞,任务先处理,实在处理不了了,再进阻塞队列)

下图是JUC中线程池处理任务的流程:
在这里插入图片描述

与JUC中明显不同的一点是,Tomcat为了处理IO,减少阻塞的情况,
本系列文章就是专门探讨Tomcat中线程池的原理,分为上下两篇,本文是上篇,主要介绍Tomcat中线程池的初始化原理。

本系列文章基于SpringBoot2.7.6,其内嵌的tomcat版本是9.0.69。
同系列文章:Tomcat线程池原理(下篇:工作原理)

正文

本系列文章核心内容是Tomcat的线程池原理,因此在画图,文字描述时会忽略部分不涉及的内容。

一、从启动脚本开始分析

使用过Tomcat的同学都知道,我们单独的启动tomcat时,是从脚本入手的。

启动tomcat , 需要调用 bin/startup.bat (在linux 目录下 , 需要调用 bin/startup.sh)
在startup.bat 脚本中, 调用了catalina.bat。
在catalina.bat 脚本文件中,调用了BootStrap 中的main方法。
后续的操作如下图:
在这里插入图片描述
简而言之,就是逐级的 init()start()
而本文的关注点,就是 ProtocolHandlerstart(),也就是图中的最后一步。

二、ProtocolHandler 的启动原理

在这里插入图片描述
关键在于 EndPointstart()

而在Tomcat 中,会执行到 AbstractEndPointstart()。具体代码如下:

public final void start() throws Exception {if (bindState == BindState.UNBOUND) {bindWithCleanup();bindState = BindState.BOUND_ON_START;}startInternal();
}public abstract void startInternal() throws Exception;

也就是说真正的启动方法是AbstractEndPoint 子类实现的startInternal()

三、AbstractEndPoint 的启动原理

在Tomcat中,有3个AbstractEndPoint的子类。
在8.5/9.0版本中,使用的是其中的 NioEndPoint类。
本文就使用默认的 NioEndPoint 进行分析。

接第二小节, NioEndPoint 在执行startInternal()时,会判断是否存在线程池,如果没有,会创建默认的线程池。对应代码如下:

@Override
public void startInternal() throws Exception {if (!running) {running = true;paused = false;if (socketProperties.getProcessorCache() != 0) {processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());}if (socketProperties.getEventCache() != 0) {eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());}if (socketProperties.getBufferPool() != 0) {nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());}// 如果没自定义线程池,则创建默认工作线程池if (getExecutor() == null) {createExecutor();}initializeConnectionLatch();// Start poller threadpoller = new Poller();Thread pollerThread = new Thread(poller, getName() + "-Poller");pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();startAcceptorThread();}
}

四、创建默认线程池

根据第三小节的分析,在没自定义线程池,或者配置线程池时,会自动创建一个线程池。代码如下:

    public void createExecutor() {internalExecutor = true;TaskQueue taskqueue = new TaskQueue();TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);taskqueue.setParent( (ThreadPoolExecutor) executor);}

注意,ThreadPoolExecutor 不是JUC中的线程池了,其是Tomcat自己实现的线程池。

五、参数配置原理

日常工作中,总会遇到需要自己制定Tomcat线程池参数的情况。这一小节就来说明一下。
在Tomcat中,TomcatWebServerFactoryCustomizer 负责配置自定义参数。

在自动配置类 EmbeddedWebServerFactoryCustomizerAutoConfiguration 中配置了如下内容:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {@Beanpublic TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,ServerProperties serverProperties) {return new TomcatWebServerFactoryCustomizer(environment, serverProperties);}
}

5.1 常规的参数配置

普通的参数配置可以参考ServerProperties 中的内容。

# Tomcat连接数相关参数
# 最大连接数,默认8192,一般要大于(tomcat.threads.max + tomcat.accept-count)
server.tomcat.max-connections=300
# 当所有工作线程都被占用时,新的连接将会放入等待队列中的最大容量,默认100
server.tomcat.accept-count=50# Tomcat线程池相关参数
# 最大线程池大小,默认200
server.tomcat.threads.max=200
# 最小工作空闲线程数(核心线程数),默认10
server.tomcat.threads.min-spare=12

5.2 自定义线程池

如果普通的参数配置,不能满足你的需求,则需要自定义线程池。

定义自己的类,继承 TomcatWebServerFactoryCustomizer ,然后重写customize即可。
核心思路是,在AbstractProtocol 中设置线程池。

以下是我的示例:

package org.feng.demos.web;import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/*** 自定义tomcat线程池** @author feng*/
@Component
public class MyTomcatWebServerFactoryCustomizer extends TomcatWebServerFactoryCustomizer {public MyTomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {super(environment, serverProperties);}@Overridepublic void customize(ConfigurableTomcatWebServerFactory factory) {super.customize(factory);// 自定义tomcat线程池System.out.println("自定义tomcat线程池--start");// 自定义tomcat线程池factory.addConnectorCustomizers((connector) -> {ProtocolHandler handler = connector.getProtocolHandler();if (handler instanceof AbstractProtocol) {AbstractProtocol protocol = (AbstractProtocol) handler;TaskQueue taskqueue = new TaskQueue();TaskThreadFactory tf = new TaskThreadFactory("feng" + "-exec-", true, 5);ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, taskqueue, tf);protocol.setExecutor(threadPoolExecutor);taskqueue.setParent(threadPoolExecutor);}});System.out.println("自定义tomcat线程池--end");}
}

5.3 测试自定义线程

定义如下方法:

// http://127.0.0.1:8080/hello?name=lisi
@RequestMapping("/hello")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {System.out.println("当前线程名:" + Thread.currentThread().getName());return "Hello " + name;
}

调用时,控制台打印:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/695497.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#知识点-13(进程、多线程、使用Socket实现服务器与客户端通信)

进程 定义&#xff1a;每一个正在运行的应用程序&#xff0c;都是一个进程 进程不等于正在运行的应用程序。而是为应用程序的运行构建一个运行环境 Process[] pros Process.GetProcesses();//获取电脑中所有正在运行的进程//通过进程&#xff0c;直接打开文件//告诉进程&…

cmd命令开启windows桌面远程控制并设置防火墙允许远程

cmd命令开启桌面远程控制 1、开启之前&#xff1a; 2、使用管理员身份运行cmd 3、执行cmd命令 reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Control\Terminal server" /v fDenyTSConnections /t REG_DWORD /d 0 /f4、如果这台电脑的防火墙打开&#xff…

Android14 InputManager-InputManagerService环境的构造

IMS分为Java层与Native层两个部分&#xff0c;其启动过程是从Java部分的初始化开始&#xff0c;进而完成Native部分的初始化。 □创建新的IMS对象。 □调用IMS对象的start&#xff08;&#xff09;函数完成启动 同其他系统服务一样&#xff0c;IMS在SystemServer中的ServerT…

股票K线认知从形态到逻辑,仓位管理与交易体系实战

一、教程描述 本套教程内容分为三个部分&#xff0c;1、基础篇&#xff1a;讲的都是干货基础&#xff0c;有些是书本上没有的&#xff0c;通过对基础知识的掌握&#xff0c;对技术形态会有更深的理解&#xff0c;比如集合竞价、K线指标、盘中看盘技巧等等。2、交易篇&#xff…

CentOS升级python

1、下载python39 https://mirrors.huaweicloud.com/python/3.9.0/Python-3.9.0.tgz2、拷贝到Linux环境&#xff08;当然也可以直接在Linux环境使用wget直接下载&#xff09; 先安装一下依赖&#xff0c;不然编译会有问题 sudo yum -y install zlib-devel bzip2-devel openssl…

【day02】每天三道 java后端面试题:Java、C++和Go的区别 | Redis的特点和应用场景 | 计算机网络七层模型

文章目录 1. Java、C和 Go 语言的区别&#xff0c;各自的优缺点&#xff1f;2. 什么是Redis&#xff1f;Redis 有哪些特点&#xff1f; Redis有哪些常见的应用场景&#xff1f;3. 简述计算机网络七层模型和各自的作用&#xff1f; 1. Java、C和 Go 语言的区别&#xff0c;各自的…

C#算法(12)—对图像像素做X/Y方向的偏移

我们在上位机开发领域有时候需要对获取的图像的像素做整体的偏移,比如所有像素在X方向上偏移几个像素,或者所有像素在Y方向上偏移几个像素,本文就是开发了像素整体偏移算法来解决这个问题。 比如有一个图像大小为3*3,像素值如下图1,如果我想实现将这个幅图像的像素整体往右…

Neon简介

欢迎关注“安全有理”微信公众号。 概述 本文介绍了 Arm Neon 技术&#xff0c;一种⾼级 SIMD&#xff08;Single Instruction Multiple Data&#xff0c;一条指令操作多个数据&#xff09;架构扩展&#xff0c;Armv8‑A 和 Armv8-R 架构支持了 Neon 技术扩展。 Neon 技术是指…

探索海洋世界,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建海洋场景下海洋生物检测识别分析系统

前面的博文中&#xff0c;开发实践过海底相关生物检测识别的项目&#xff0c;对于海洋场景下的海洋生物检测则很少有所涉及&#xff0c;这里本文的主要目的就是想要开发构建基于YOLOv7不同系列参数模型的海洋场景下的海洋生物检测识别系统。 前文已有相关实践&#xff0c;感兴…

如何在debian上实现一键恢复操作系统?

在Debian或任何其他Linux发行版上实现一键恢复操作系统&#xff0c;需要创建一个系统镜像或快照&#xff0c;并设置一个简单的方法来从该镜像恢复。以下是创建和恢复系统的基本步骤&#xff1a; 1. 创建系统镜像&#xff1a; 使用像dd&#xff0c;rsync或专门的备份工具&#…

STM32—触摸键

目录 1 、 电路构成及原理图 2 、编写实现代码 3、代码讲解 4、烧录到开发板调试、验证代码 5、检验效果 此笔记基于朗峰 STM32F103 系列全集成开发板的记录。 1 、 电路构成及原理图 触摸键简单的了解就是一次电容的充放电过程。从原理图可以看出&#xff0c;触摸键 …

4.网络游戏逆向分析与漏洞攻防-游戏启动流程漏洞-模拟游戏登陆器启动游戏并且完成注入

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;游戏启动流程的分析 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;bcf7559184863febdcad819e48aaacad9f25d633 代码下…

C#上位机与三菱PLC的通信09---开发自己的通讯库(A-3E版)

1、A-3E报文回顾 具体细节请看&#xff1a; C#上位机与三菱PLC的通信05--MC协议之QnA-3E报文解析 C#上位机与三菱PLC的通信06--MC协议之QnA-3E报文测试 2、为何要开发自己的通讯库 前面开发了自己的A-1E协议的通讯库&#xff0c;实现了数据的读写&#xff0c;对于封装的通…

Kubernetes二进制搭建

目录 1.操作系统初始化配置&#xff08;所有节点同此操作&#xff09; 2.部署etcd集群 etcd概述 准备签发证书环境 在master01节点上操作&#xff08;192.168.88.22&#xff09; 在两个node节点上操作 总结&#xff1a; 3.部署docker引擎 4.部署Master组件 总结&…

SQL语法-DQL-测试练习

因篇幅原因&#xff0c;本篇承接此篇->第八篇&#xff1a;SQL语法-DQL-数据查询语言-CSDN博客 本篇是对于SQL语法DQL语句的练习&#xff0c;因水平和精力有限&#xff08;就不像前两篇的DDL&#xff0c;DML那样自出练习了&#xff09;直接照搬了【黑马程序员】在哔哩哔哩的…

有影响因子的《科教文汇》2024投稿攻略

《科教文汇》主要设有八面来风、教育观察、卷首语、教育管理、思政教育、教改教法、课程思政、基础教育、职业教育等栏目。 主管单位 安徽省科学技术协会 主办单位 安徽省老科技工作者协会、安徽省科学教育研究会 国内统一刊号CN34-1274/G&#xff0c; 国际标准刊号ISSN 16…

基于 Fluid+JindoCache 加速大模型训练的实践

作者&#xff1a;王涛(扬礼)、陈裘凯(求索)、徐之浩(东伝) 背景 时间步入了 2024 年&#xff0c;新的技术趋势&#xff0c;如大模型/AIGC/多模态等技术&#xff0c;已经开始与实际业务相结合&#xff0c;并开始生产落地。这些新的技术趋势不仅提高了算力的需求&#xff0c;也…

搭建XSS 测试平台

XSS 测试平台是测试XSS漏洞获取cookie并接收Web 页面的平台&#xff0c;XSS 可以做 JS能做的所有事&#xff0c;包括但不限于窃取cookie、后台增删改文章、钓鱼、利用XSS漏洞进 行传播、修改网页代码、网站重定向、获取用户信息(如浏览器信息、IP 地址)等。这 里使用的是基于x…

Windows下搭建EFK实例

资源下载 elasticSearch &#xff1a;下载最新版本的就行 kibana filebeat&#xff1a;注意选择压缩包下载 更新elasticsearch.yml&#xff0c;默认端口9200&#xff1a; # Elasticsearch Configuration # # NOTE: Elasticsearch comes with reasonable defaults for most …

Rust Vs Go:从头构建一个web服务

Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而&#xff0c;最终的决定性因素是重要项目的易用性。 “Rust 与 Go”争论 Rust vs Go 是一个不断出现的话题&#xff0c;并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下…