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 thread
        poller = 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 {

	@Bean
	public 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);
    }

    @Override
    public 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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

挑战杯 基于YOLO实现的口罩佩戴检测 - python opemcv 深度学习

文章目录 0 前言1 课题介绍2 算法原理2.1 算法简介2.2 网络架构 3 关键代码4 数据集4.1 安装4.2 打开4.3 选择yolo标注格式4.4 打标签4.5 保存 5 训练6 实现效果6.1 pyqt实现简单GUI6.3 视频识别效果6.4 摄像头实时识别 7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xf…

泛微e-office系统存在敏感信息泄露 附POC软件

免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 泛微e-office系统简介 微信公众号搜索:南风漏洞复…

车载软件架构Adaptive AUTOSAR —— 身份和访问管理和加密技术

车载软件架构Adaptive AUTOSAR —— 身份和访问管理和加密技术 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。…

探索NFC技术在游戏玩具娱乐,医疗保健和穿戴设备领域的三大应用

NFC是与众不同的无线技术。这意味着它只能在两个设备相近时起作用。在其他用无线技术随机广播的方式以被接收时&#xff0c;NFC更重要的独特之处于其使用电源的方式。或者&#xff0c;更确切地说&#xff0c;它可以在不供电的环境下进行工作。它是一种非接触式智能卡技术的演进…

挑战30天学完Python:Day16 日期时间

&#x1f4d8; Day 16 &#x1f389; 本系列为Python基础学习&#xff0c;原稿来源于 30-Days-Of-Python 英文项目&#xff0c;大奇主要是对其本地化翻译、逐条验证和补充&#xff0c;想通过30天完成正儿八经的系统化实践。此系列适合零基础同学&#xff0c;或仅了解Python一点…

干货分享 | TSMaster 序列发送模块在汽车开发测试中的应用

众所周知&#xff0c;序列发送模块可以不需要脚本代码实现测试中特定控制报文序列的发送&#xff0c;该模块多用于循环顺序控制的测试案例中。序列发送模块的常用场景&#xff0c;主要是针对一些新开发的产品需要通过该模块来验证产品功能等等。本文重点和大家分享一下关于TSMa…

STM32—启用按键

​ 目录 1 、电路构成及原理图 2、编写实现代码 main.c main.h key.c 3、代码讲解 4、 烧录到开发板调试、验证代码 5、检验效果 本人使用的是朗峰 STM32F103 系列开发板&#xff0c;此笔记基于这款开发板记录。 1 、电路构成及原理图 重要&#xff01;一定先用短路…

ZFS存储池速度以及RAID说明

文章目录 前言1. STRIPED VDEV&#xff08;RAID 0&#xff09;性能示例&#xff1a; 2. MIRRORED VDEV &#xff08;RAID 1&#xff09;1x 12-way mirror:6x 2-way mirror:4x 3-way mirro&#xff1a;性能示例&#xff1a; 3. RAIDZ VDEVRAIDZ3:RAIDZ2&#xff08;RAID6&#x…

css实现梯形

<div class"trapezoid"></div> .trapezoid {width: 200px;height: 0;border-bottom: 100px solid red; /* 定义梯形的底边 */border-left: 50px solid transparent; /* 定义梯形的左边 */border-right: 50px solid transparent; /* 定义梯形的右边 */} …

【RN】开发第一个react-native程序

简言 React Native 是一个使用React和应用平台的原生功能来构建 Android 和 iOS 应用的开源框架。通过 React Native&#xff0c;您可以使用 JavaScript 来访问移动平台的 API&#xff0c;以及使用 React 组件来描述 UI 的外观和行为&#xff1a;一系列可重用、可嵌套的代码。…

代码随想录算法训练营第十九天|认识回溯,77.组合

认识回溯 77.组合 认识回溯 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 回溯法的效率 回溯的本质是穷举&#xff0c;即使加了剪枝操作&#xff0c;其本质也还是穷举 回溯法解决的问题 组合问题…

【2024软件测试面试必会技能】Jmeter_性能测试(4):性能测试脚本的优化

性能测试脚本的优化 以PHP论坛为例&#xff1a;http://47.107.178.45/phpwind/ 根据上一篇的性能测试(3&#xff09;的脚本进行优化&#xff1b;见下图&#xff1a; 如上图中&#xff0c;把发帖和回帖的事务添加到随机控制器中&#xff0c;登录操作添加到仅一次控制器中&…

Python学习笔记——自定义函数(传递任意数量的实参)

Python允许函数从调用语句中收集任意数量的实参。例如下面自定义函数制作一个披萨&#xff0c;它需要接受很多配料&#xff0c;但无法预先确定顾客要点多少种配料。 下面行数只有一个形参*toppings&#xff0c;不管调用语句提供多少个实参&#xff0c;这个参数都会收集到&…

阿里云/腾讯云幻兽帕鲁服务器为什么更新/重启之后,服务器存档没了?

有的朋友说&#xff0c;他的阿里云幻兽帕鲁服务器重启了一下后&#xff0c;服务器存档就没了&#xff1f;这是怎么回事呢&#xff0c;其实可能的原因&#xff0c;一是服务器还有重启完成&#xff0c;也就是游戏服务端还没有启动&#xff0c;就登进去&#xff0c;可能会显示网络…

智慧公厕是什么?智慧公厕对智慧城市的意义

城市的信息化发展需要催化了智慧城市&#xff0c;公共厕所作为城市的重要民生设施&#xff0c;如何实现更高阶的信息化建设&#xff0c;成为一个重要课题。那么&#xff0c;智慧公厕是什么&#xff1f;为什么它对智慧城市的建设如此重要&#xff1f;本文以智慧公厕源头厂家广州…

git工具

一、命令行工具 二、Git 客户端可视化工具-推荐 1.常用工具 tortoisegit 官网 https://tortoisegit.org/ 推荐 sourcetree 官网https://www.sourcetreeapp.com/ 2.tortoisegit安装 2.1 下载安装包 2.2 下载语言包 2.3 安装 2.4 安装语言包 5.使用 5.1 新建分支 5.2 切换分支…

力扣精选算法100道——Z字形变换(模拟专题)

目录 &#x1f388;了解题意 &#x1f388;算法原理 &#x1f6a9;先处理第一行和最后一行 &#x1f6a9;再处理中间行 &#x1f388;实现代码 &#x1f388;了解题意 大家看到这个题目的时候肯定是很迷茫的&#xff0c;包括我自己也是搞不清楚题目什么意思&#xff0c;我…

R cox回归 ggDCA报错

临床预测模型的决策曲线分析&#xff08;DCA&#xff09;&#xff1a;基于ggDCA包 决策曲线分析法&#xff08;decision curve analysis&#xff0c;DCA&#xff09;是一种评估临床预测模型、诊断试验和分子标记物的简单方法。 我们在传统的诊断试验指标如&#xff1a;敏感性&a…

游戏同步+游戏中的网络模块

原文链接&#xff1a;游戏开发入门&#xff08;九&#xff09;游戏同步技术_游戏数据同步机制流程怎么开发-CSDN博客 游戏开发入门&#xff08;十&#xff09;游戏中的网络模块_游戏开发组网-CSDN博客 3.同步技术的基本常识&#xff1a; a.同步给谁&#xff1f;某个用户&…

Day04 嵌入式---基本定时器

定时器概述 1、软件定时原理 使⽤纯软件的⽅式实现定时功能。 存在的问题&#xff1a;定时不太精准。CPU死等。 1&#xff09;压栈出栈需要花费时间 2&#xff09;ARM流⽔线体系架构的原因 2、定时器定时原理 使用精准的时基&#xff0c;通过硬件方式&#xff0c;实现定…