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

这篇具有很好参考价值的文章主要介绍了Tomcat线程池原理(上篇:初始化原理)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

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

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

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

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

下图是JUC中线程池处理任务的流程:
Tomcat线程池原理(上篇:初始化原理),java源码,web框架学习,tomcat,线程池原理,tomcat线程池初始化

与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方法。
后续的操作如下图:
Tomcat线程池原理(上篇:初始化原理),java源码,web框架学习,tomcat,线程池原理,tomcat线程池初始化
简而言之,就是逐级的 init()start()
而本文的关注点,就是 ProtocolHandlerstart(),也就是图中的最后一步。

二、ProtocolHandler 的启动原理

Tomcat线程池原理(上篇:初始化原理),java源码,web框架学习,tomcat,线程池原理,tomcat线程池初始化
关键在于 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;
}

调用时,控制台打印:
Tomcat线程池原理(上篇:初始化原理),java源码,web框架学习,tomcat,线程池原理,tomcat线程池初始化文章来源地址https://www.toymoban.com/news/detail-835635.html

到了这里,关于Tomcat线程池原理(上篇:初始化原理)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • canal server初始化源码分析

    在开始之前,我们可以先了解下, canal 配置方式 ManagerCanalInstanceGenerator: 基于manager管理的配置方式,实时感知配置并进行server重启 SpringCanalInstanceGenerator:基于本地spring xml的配置方式,对于多instance的时候,不便于扩展,一个instance一个xml配置 canal 配置文件 canal.properties  

    2024年01月19日
    浏览(34)
  • SpringMVC源码解析——DispatcherServlet初始化

    在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型的实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现Servlet接口的实现类。Servlet是一个JAVA编写的程序,此程序是基于HTTP协议的,在服务端运行的(如Tomcat),是按照Servlet规范

    2024年02月03日
    浏览(35)
  • UE4 源码解析----引擎初始化流程

       在研究UE4的源码过程中着实不理解的地方有很多,今天给大家分享一下UE4引擎的初始化流程。 C++的函数入口都是Main() 函数入口,UE4也是一样,EngineSourceRuntimeLaunchPrivate Windows函数入口  引擎入口函数为:GuardedMain UE4所有相关的代码都在游戏循环函数中,在Launch.cpp中,写

    2024年02月06日
    浏览(78)
  • 【STM32&RT-Thread零基础入门】 5. 线程创建应用(线程创建、删除、初始化、脱离、启动、睡眠)

    硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线 本章主要讲线程的工作机制和管理方法,通过实例讲解如何使用多线程完成多任务开发。 RT-Thread用线程控制块来描述和管理一个线程,一个线程对应一个线程控制块。线程控

    2024年02月12日
    浏览(44)
  • 『手撕 Mybatis 源码』06 - Mapper 代理方式初始化

    首先修改一下 SqlSession 获取代理对象方式,即通过 getMapper() 来拿到动态代理对象 修改 sqlMapConfig.xml 引入配置文件的方式 把 UserMapper.xml 放到和 com.itheima.mapper.UserMapper 同一个目录,同时修改一下命名空间,然后就可以学习 MyBatis 的代理方式 问题 package name=“com.itheima.mapper”/

    2024年02月09日
    浏览(37)
  • 【源码解析】聊聊SpringBean是如何初始化和创建

    我们知道通过类进行修复不同的属性,比如单例、原型等,而具体的流程是怎么样的呢,这一篇我们开始从源码的视角分析以下。 在刷新容器中有一个方法,其实就是 Bean创建的过程。 而BeanFactory中 preInstantiateSingletons是初始化所有的bean对象的核心流程。 而这里通过去遍历所

    2024年02月05日
    浏览(33)
  • Spring 填充属性和初始化流程源码剖析及扩展实现

    在上一篇博文 讲解 Spring 实例化的不同方式及相关生命周期源码剖析 介绍了 Spring 实例化的不同方式,本文主要围绕实例化过后对象的填充属性和初始化过程进行详细流程剖析 回顾前言知识,doCreateBean-createBeanInstance,通过 Supplier 接口、FactoryMethod、构造函数反射 invoke,创建

    2024年02月06日
    浏览(33)
  • SpringBoot 源码分析初始化应用上下文(1)-createApplicationContext

    前言:springBoot的版本是  2.2.4.RELEASE 问题切入:为什么叫做上下文对象呢?上下文对象,就是当前应用环境下的一个集合 初始化(创建)上下文对象主要看上面注释那行,即: 接着看下 createApplicationContext() 这个方法的实现 截图: 代码:  接着看下AnnotationConfigServletWebServerApp

    2024年02月08日
    浏览(32)
  • Java的初始化块

    三种初始化数据域的方法: 在构造器中设置值 在声明中赋值 初始化块(initialization block) 在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行。 在上面这个示例中,无论使用哪个构造器构造对象,id 域都在对象初始化块中被初始化。首先运行初

    2023年04月27日
    浏览(37)
  • 从内核源码看 slab 内存池的创建初始化流程

    在上篇文章 《细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现 》中,笔者从 slab cache 的总体架构演进角度以及 slab cache 的运行原理角度为大家勾勒出了 slab cache 的总体架构视图,基于这个视图详细阐述了 slab cache 的内存分配以及释放原理。 slab cache 机制确实比

    2023年04月12日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包