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.rhkb.cn/news/265268.html

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

相关文章

书生·浦语大模型全链路开源体系介绍

背景介绍 随着人工智能技术的迅猛发展&#xff0c;大模型技术已成为当今人工智能领域的热门话题。2022 年 11 月 30 日&#xff0c;美国 OpenAI 公司发布了 ChatGPT 通用型对话系统 并引发了全球 的极大关注&#xff0c;上线仅 60 天月活用户数便超过 1 亿&#xff0c;成为历史…

密码学系列(四)——对称密码2

一、RC4 RC4&#xff08;Rivest Cipher 4&#xff09;是一种对称流密码算法&#xff0c;由Ron Rivest于1987年设计。它以其简单性和高速性而闻名&#xff0c;并广泛应用于网络通信和安全协议中。下面是对RC4的详细介绍&#xff1a; 密钥长度&#xff1a; RC4的密钥长度可变&am…

Python爬虫实战入门:爬取360模拟翻译(仅实验)

文章目录 需求所需第三方库requests 实战教程打开网站抓包添加请求头等信息发送请求&#xff0c;解析数据修改翻译内容以及实现中英互译 完整代码 需求 目标网站&#xff1a;https://fanyi.so.com/# 要求&#xff1a;爬取360翻译数据包&#xff0c;实现翻译功能 所需第三方库 …

【OnlyOffice】 桌面应用编辑器,版本8.0已发布,PDF表单、RTL支持、Moodle集成、本地界面主题

ONLYOFFICE桌面编辑器v8.0是一款功能强大、易于使用的办公软件&#xff0c;适用于个人用户、企业团队和教育机构&#xff0c;帮助他们高效地处理文档工作并实现协作。无论是在Windows、macOS还是Linux平台上&#xff0c;ONLYOFFICE都能提供无缝的编辑和共享体验。 目录 ONLYOFF…

Jmeter接口测试+压力测试

Jmeter-http接口脚本 一般分五个步骤:&#xff08;1&#xff09;添加线程组 &#xff08;2&#xff09;添加http请求 &#xff08;3&#xff09;在http请求中写入接入url、路径、请求方式和参数 &#xff08;4&#xff09;添加查看结果树 &#xff08;5&#xff09;调用接口、…

化学分子Mol2文件格式与使用注意事项

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、Mol2文件示例二、 Mol2文件主要结构解释及注意事项MOLECULE 字段解释ATOM 字段解释BOND 字段解释SUBSTRUCTURE字段解释 总结参考资料 前言 Mol2格式文件是一个ASCII 文件&#xff0c;由Tripos…

STM32控制max30102读取血氧心率数据(keil5工程)

一、前言 MAX30102是一款由Maxim Integrated推出的低功耗、高精度的心率和血氧饱和度检测传感器模块&#xff0c;适用于可穿戴设备如智能手环、智能手表等健康管理类电子产品。 该传感器主要特性如下&#xff1a; &#xff08;1&#xff09;光学测量&#xff1a;MAX30102内置…

Rocky Linux 运维工具yum

一、yum的简介 ​​yum​是用于在基于RPM包管理系统的包管理工具。用户可以通过 ​yum​来搜索、安装、更新和删除软件包&#xff0c;自动处理依赖关系&#xff0c;方便快捷地管理系统上的软件。 二、yum的参数说明 1、install 用于在系统的上安装一个或多个软件包 2、seach 用…

Docker 常用操作命令备忘

Docker 一旦设置好了环境&#xff0c;日常就只要使用简单命令就可以运行和停止。 于是&#xff0c;我每次用的时候&#xff0c;都想不起来一些关键性的命令到底怎么用&#xff0c;特此记录。 一、镜像管理 从公有仓库拉取镜像 &#xff08;对于使用苹果电脑 M1/M2/M3 芯片的 …

完全分布式运行模式

完全分布式运行模式 分析&#xff1a;之前已经配置完成 ​ 1&#xff09;准备3台客户机&#xff08;关闭防火墙、静态ip、主机名称&#xff09; ​ 2&#xff09;安装JDK ​ 3&#xff09;配置环境变量 ​ 4&#xff09;安装Hadoop ​ 5&#xff09;配置环境变量 ​ 6&am…

Springboot集成Druid实现监控功能

Druid是阿里巴巴开发的号称为监控而生的数据库连接池&#xff0c;在功能、性能、扩展性方面&#xff0c;都超过其他数据库连接池&#xff0c;包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource等等等&#xff0c;秒杀一切。Druid可以很好的监控DB池连接和SQL的执行情况&#…

AOSP10 替换系统launcher

本文实现将原生的launcher 移除&#xff0c;替换成我们自己写的launcher。 分以下几个步骤&#xff1a; 一、新建一个自己的launcher项目。 1.直接使用android studio 新建一个项目。 2.修改AndroidManifest.xml <applicationandroid:persistent"true"androi…

nginx实现http反向代理

一、代理概述 1、代理概念 1.1 正向代理&#xff08;Forward Proxy&#xff09; 概念&#xff1a;正向代理是位于客户端和目标服务器之间的代理服务器&#xff0c;代表客户端向目标服务器发送请求。客户端将请求发送给代理服务器&#xff0c;然后代理服务器将请求转发给目标服…

Prompt 编程的优化技巧

一、为什么要优化 一&#xff09;上下文限制 目前 GPT-3.5 以及 GPT-4最大支持 16K 上下文&#xff0c;比如你输入超过 16k 的长文本&#xff0c;ChatGPT 会提示文本过大&#xff0c;为了避免 GPT 无法回复&#xff0c;需要限制 上下文在16k 以内 上下文对于 GPT 来说是非常重…

【手机端测试】adb基础命令

一、什么是adb adb&#xff08;Android Debug Bridge&#xff09;是android sdk的一个工具 adb是用来连接安卓手机和PC端的桥梁&#xff0c;要有adb作为二者之间的维系&#xff0c;才能让用户在电脑上对手机进行全面的操作。 Android的初衷是用adb这样的一个工具来协助开发人…

第103讲:配置Mycat的Schema逻辑库列表

文章目录 1.Schema逻辑库2.自定义Mycat连接后显示那些Schema 1.Schema逻辑库 使用Mycat登录到数据库后&#xff0c;发现仅显示了一个TESTDB&#xff0c;这个TESTDB并不是后台数据库节点中的数据库&#xff0c;只是Mycat定义的逻辑库Schema&#xff0c;接下来我们就来说明如果自…

蓝桥杯Learning

Part 1 递归和递推 1. 简单斐波那契数列 n int(input())st [0]*(47) # 注意这个地方&#xff0c;需要将数组空间设置的大一些&#xff0c;否则会数组越界 st[1] 0 st[2] 1 # 这个方法相当于是递推&#xff0c;即先求解一个大问题的若干个小问题 def dfs(u):if u 1:print(…

CKA认证,开启您的云原生之旅!

在当今数字化时代&#xff0c;云计算已经成为企业和个人发展的关键技术。而获得CKA&#xff08;Certified Kubernetes Administrator&#xff09;认证&#xff0c;将是您在云原生领域迈出的重要一步。 CKA认证是由Kubernetes官方推出的权威认证&#xff0c;它旨在验证您在Kuber…

OSI模型

OSI模型 TCP/IP参考模型 TCP/IP常见协议 应用层 FTP&#xff08;用于文件的下载和上传&#xff0c;采用C/S结构&#xff09; Telnet&#xff08;用于远程登陆服务&#xff09; DNS&#xff08;域名解析&#xff09; HTTP&#xff08;接收和发布Html页面&#xff09; 传输层…

MetaGPT 1 安装与配置踩坑实录

安装 与 配置直接参考这里就行&#xff1a;Hugging Muti Agent&#xff08;二月学习&#xff09; - 飞书云文档 (feishu.cn) 这里按照教程安装的是metagpt 0.6.6 &#xff0c;经过跟0.7.0对比&#xff0c;个人认为0.7对其他llm接入可能更好&#xff0c;文档也更清晰。 0.6.6的…