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.hqwc.cn/news/483350.html

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

相关文章

【Java EE初阶二十一】http的简单理解(二)

2. 深入学习http 2.5 关于referer Referer 描述了当前页面是从哪个页面跳转来的&#xff0c;如果是直接在地址栏输入 url(或者点击收藏夹中的按钮) 都是没有 Referer。如下图所示&#xff1a; HTTP 最大的问题在于"明文传输”,明文传输就容易被第三方获取并篡改. …

【Node.js】介绍、下载及安装

目录 一、什么是 Node.js 二、Node.js下载 下载方式1&#xff1a;直接在首页下载&#xff08;下载的是.msi后缀的安装包&#xff09; 下载方式2&#xff1a;点击官网顶上的DOWNLOAD 三、Node.js安装 .zip后缀的安装步骤 .msi后缀的安装步骤 一、什么是 Node.js Node.js …

Sora一出,世界将变!

近日&#xff0c;OpenAI突然发布的首个文生视频模型&#xff1a;Sora&#xff08;Sora 在日语中是“天空”的意思&#xff0c;引申含义还有“自由”的意思&#xff09;&#xff0c;大幅刷新行业多个指标&#xff0c;重新定义了AI文生视频在现阶段的技术极限&#xff0c;颠覆了生…

解决docker中运行的jar包连不上前端程序

目录 检查端口映射 查看容器的 IP 地址 检查容器网络设置 防火墙和网络策略 前端程序配置 跨域资源共享 (CORS) 日志查看 连接问题通常涉及到网络配置和端口映射。确保你在 Docker 中运行的 JAR 包可以被前端程序访问&#xff0c;可以采取以下步骤来解决问题&#xff1a…

使用HHDBCS,快速修改GIS数据

GIS&#xff08;地理信息系统&#xff09;是一种基于计算机的工具&#xff0c;它可以对空间信息进行分析和处理&#xff08;简而言之&#xff0c;是对地球上存在的现象和发生的事件进行成图和分析&#xff09;。 GIS 技术把地图这种独特的视觉化效果和地理分析功能与一般的数据…

模型转换案例学习:等效替换不支持算子

文章介绍 Qualcomm Neural Processing SDK &#xff08;以下简称SNPE&#xff09;支持Caffe、ONNX、PyTorch和TensorFlow等不同ML框架的算子。对于某些特定的不支持的算子&#xff0c;我们介绍一种算子等效替换的方法来完成模型转换。本案例来源于https://github.com/quic/qidk…

Leo赠书活动-17期 《基础软件之路:企业级实践及开源之路》

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

​LeetCode解法汇总106. 从中序与后序遍历序列构造二叉树

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; 给定两个…

【python】python入门之条件判断及某些报错的含义

目录 示例 细节报错​编辑 判断符号 嵌套判断 示例 mood int(input()) if mood > 60:print("happy!!") else:print("just so so") 通过示例看出&#xff0c;python的写法和c的区别不是很大&#xff0c;if和else都作为条件判断的函数来使用&…

华为OD机试真题C卷-篇4

200分值题 可以处理的最大任务员工派遣快递员的烦恼符号运算伐木工反射计数分披萨推荐多样性贪心的歌手螺旋数组矩阵(100) 可以处理的最大任务 有一个tasks任务列表&#xff0c;需要处理其中的任务&#xff1b;tasks[i] [si, ei]&#xff0c;该任务可以在si<day<ei之间…

【PX4SimulinkGazebo联合仿真】在Simulink中使用ROS2控制无人机沿自定义圆形轨迹飞行并在Gazebo中可视化

在Simulink中使用ROS2控制无人机沿自定义圆形轨迹飞行并在Gazebo中可视化 系统架构Matlab官方例程Control a Simulated UAV Using ROS 2 and PX4 Bridge运行所需的环境配置PX4&Simulink&Gazebo联合仿真实现方法建立Simulink模型并完成基本配置整体框架各子系统实现原理…

mp4转换成mp3,三种方法教你提取视频中的音乐

在日常生活中&#xff0c;我们经常会遇到想要从某个MP4视频中提取出背景音乐或对话的情况。将视频转换为MP3格式&#xff0c;不仅方便我们在各种设备上播放&#xff0c;还能节省存储空间。然而&#xff0c;对于不熟悉视频处理的人来说&#xff0c;这可能是一个挑战。 幸运的是…