tomcat线程池-深度分析tomcat线程池设计与现实

news2025/2/28 15:35:32

1.概述

       在正式进入Tomcat线程池之前,小伙伴们可以先回顾一下JDK中的线程池相关特性,对于JDK线程池的总结和源码的解析感兴趣的童鞋,也可参考博主的层层剖析线程池源码的这篇文章,文章主要讲述对线程池的生命周期核心参数常用线程池以及线程池源码做了详细的介绍。
        tomcat内部线程池的实现没有直接使用JUC下的ThreadPoolExecutor,而是选择继承JUC下的Executor体系类,然后重写execute()等方法,如下,为tomcat7.x下的线程池类图:
在这里插入图片描述

  • tomcat的线程池不仅继承了JDK线程池Executor框架,同时为了将Executor纳入Lifecycle生命周期管理,也让它实现了Lifecycle接口
  • tomcat线程池对外暴露

注意:不同版本下的tomcat,继承体系略有不同:

  1. 继承JUC原生ThreadPoolExecutor(7.x版本及以下),并覆写了一些方法,主要execute()和afterExecute()

  2. 继承JUC的AbstractExecutorService(7.x版本以上),从java.util.concurrent.ThreadPoolExecutor
    复制出一套代码,然后微调内部的execute()方法

2. Tomcat 线程池源码

2.1 线程池初始化

  protected void startInternal() throws LifecycleException {
        // 1.任务队列 
        taskqueue = new TaskQueue(maxQueueSize);
        // 2. 线程工厂
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
        // 3. 创建线程池,
        // ThreadPoolExecutor为 org.apache.tomcat.util.threads.ThreadPoolExecutor
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
        // 重建线程的时间间隔
        executor.setThreadRenewalDelay(threadRenewalDelay);
        // 是否需要预热核心线程
        if (prestartminSpareThreads) {
            executor.prestartAllCoreThreads();
        }
        // 线程池任务队列的 parent ,重点属性
        taskqueue.setParent(executor);
        // 设置组件的生命周期状态,lifcle 管理
        setState(LifecycleState.STARTING);
    }

在这个方法中,主要是初始化初始化org.apache.tomcat.util.threads.TaskQueue,同时构造org.apache.tomcat.util.threads.ThreadPoolExecutor的实例,org.apache.tomcat.util.threads.ThreadPoolExecutor构造方法与Java原生的线程池并没有任何不同,核心逻辑是直接调用父类[java.util.concurrent.ThreadPoolExecutor]的构造方法,只不过,在调用父类的构造方法之后,又调用了prestartAllCoreThreads()方法,用来预热核心线程。

2.2 核心逻辑-任务执行

在进入核心executor方法之前,先上图,演示tomcat线程池中的线程池是怎么处理提交过来的任务,小伙伴可仔细观察与原生JDK线程处理有何异同:
在这里插入图片描述

        是不是很奇怪,与我们JDK线程池老八股文貌似有很大不同,这里是核心线程数如果满了,就判断最大线程数是否已经满了,如果没有满,则创建最大线程数;而JDK的原生线程池的的处理流是:

  • 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  • 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  • 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建 并启动一个线程来执行新提交的任务。
  • 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常

带着这个问题,直接看核心executor方法:

    public void execute(Runnable command, long timeout, TimeUnit unit) {
        if ( executor != null ) {
            // 调用org.apache.tomcat.util.threads.ThreadPoolExecutor
            executor.execute(command,timeout,unit);
        } else {
            throw new IllegalStateException("StandardThreadExecutor not started.");
        }
    }
    // org.apache.tomcat.util.threads.ThreadPoolExecutor
    public void execute(Runnable command, long timeout, TimeUnit unit) {
        // 线程任务数加q
        submittedCount.incrementAndGet();
        try {
            // 父类:java.util.concurrent.ThreadPoolExecutor#execute()方法
            super.execute(command);
        } catch (RejectedExecutionException rx) {
            if (super.getQueue() instanceof TaskQueue) {
                // 如果调用父类中的方法执行,会尝试将任务再一次放入到等待队列里
                final TaskQueue queue = (TaskQueue)super.getQueue();
                try {
                    if (!queue.force(command, timeout, unit)) {
                       // 如果也失败了,就回滚提交计数,并抛出异常
                        submittedCount.decrementAndGet();
                        throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                    }
                } catch (InterruptedException x) {
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(x);
                }
            } else {
                submittedCount.decrementAndGet();
                throw rx;
            }

        }
    }

        可能看了一遍,也没发现有啥不同,Tomcat的线程池不就是调用原生的JDK线程池,然后在原生线程池执行拒绝策略的时候,尝试将任务再入一次任务队列,看起来貌似也没有毛病,不知道小伙伴有没有想起我们线程池的七大参数,在我们tomcat初始化的时候,传入的七大参数分别是什么:在Tomcat的线程池初始化的时候,其中尤其的阻塞队列,tomcat自己实现了的阻塞队列:org.apache.tomcat.util.threads.TaskQueue,其实问题就出现在该阻塞队列的实现上,下面,我们再看看org.apache.tomcat.util.threads.TaskQueue的源码

2.2.1 org.apache.tomcat.util.threads.TaskQueue

org.apache.tomcat.util.threads.TaskQueue 继承java.util.concurrent.LinkedBlockingQueuejava.util.concurrent.LinkedBlockingQueue 在初始化的时候,如果不指定默认长度,则其默认长度为Integer.MAX_VALUE

2.2.1.2 TaskQueue#offer方法
    public boolean offer(Runnable o) {
      //we can't do any checks
        if (parent==null) {
          return super.offer(o);
        }
        //we are maxed out on threads, simply queue the object
        //核心线程数等于最大线程数时
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) {
          return super.offer(o);
        }
        // 提交任务的个数小于核心线程数
        if (parent.getSubmittedCount()<=(parent.getPoolSize())) {
          return super.offer(o);
        }
        // 这种情况下线程池可以直接消费任务,无需放入任务队列等待,当核心线程数小于最大线程数
        // 时候,直接返回false
        if (parent.getPoolSize()<parent.getMaximumPoolSize()) {
          return false;
        }
        //if we reached here, we need to add it to the queue
        return super.offer(o);
    }

        为什么分析org.apache.tomcat.util.threads.TaskQueue#offer() 方法呢?因为在JDK原生线程池中,如果核心线程数已满,则会调用阻塞队列的offer() 方法向队列添加任务,如果添加失败,则创建核心最大线程来处理任务,试想,此处,如果最大线程数大于核心线程数,提交任务的时候,是不是直接就返回false,然后创建最大线程数的线程来处理任务,当最大线程数满的时候,执行拒绝策略-可参考层层剖析线程池源码第3章节源码解析,JDK原生线程池抛出异常,tomcat线程池然后通过org.apache.tomcat.util.threads.TaskQueue#force()方法添加任务:如下:为org.apache.tomcat.util.threads.TaskQueue#force()源码:

public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
        if (this.parent != null && !this.parent.isShutdown()) {
            return super.offer(o, timeout, unit);
        } else {
            throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
        }
 }

         org.apache.tomcat.util.threads.TaskQueue#force() 方法很简单,本质就是调用java.util.concurrent.LinkedBlockingQueueoffer() 方法,这样是不是很巧妙的修改了JDK原生线程池的执行流程

2.3 tomcat线程池默认属性

属性描述
daemon(boolean)线程是否应该是守护程序线程,默认为 true
namePrefix字符串)执行程序创建的每个线程的名称前缀。单个线程的线程名称将是namePrefix+threadNumber
maxThreads(int)此池中活动线程的最大数量,默认为 200
minSpareThreads(int)最小线程数(空闲和活动)始终保持活动状态,默认为 25
maxIdleTime(int)空闲线程关闭之前的毫秒数,除非活动线程数小于或等于minSpareThreads。默认值为60000(1分钟)
maxQueueSize(int)在我们拒绝之前可以排队等待执行的可运行任务的最大数量。默认值是Integer.MAX_VALUE

3.总结

tomcat 的线程池封装其实并不厚重,只是对 jdk 线程池做了简单优化,当线程池没有达到最大执行线程的时候,会优先开线程处理而不是直接将任务放入任务队列中,可能这样做,本身也是符合Tomcat的特性,因为Tomcat的处理的任务是IO请求,如果IO请求都是先放到任务队列等待,然后再处理,可能会极大降低tomcat的IO处理效率,这也是博主个人的思考,有不同看法的,欢迎留下评论,一起探讨。

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

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

相关文章

五、事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制

目标 深入理解和掌握事件的冒泡及捕获机制理解react中的合成事件的本质在react组件中合理的使用原生事件 知识点 在原生的dom模型上触发的事件会进行事件传递。而所谓的事件传递指的是当在A元素上触发某一事件的时候&#xff0c;B元素如果满足了和A元素一定的关系的话&#xf…

freeswitch如何判断挂机方

概述 freeswitch作为VOIP的软交换平台&#xff0c;需要对呼叫的信息做判断和归类。 常见的呼叫信息中&#xff0c;挂机方向的信息对于话单统计有很大的用处。 但是fs的原始话单和日志中并没有挂机方向的信息。 环境 centos&#xff1a;CentOS7 freeswitch&#xff1a;v1.…

火遍国内外IT技术圈,豆瓣 9.7!这本技术书籍直接封神了

这是一本在国内外都被称为技术书籍巅峰之作的神书&#xff01; 国内的豆瓣评分 9.7&#xff08;满分 10.00&#xff09;&#xff0c;接近 90% 的人为这本书打了五星好评。 国外的 Goodreads&#xff08;类似于豆瓣&#xff09; 评分 4.72&#xff08;满分 5.00&#xff09; &a…

元宇宙:下一代互联网的“美丽新世界”

8月6日到8日,美国超人气歌手Ariana Grande在全球不同地区,连续举办了5场演唱会。与众不同的是,演出的举办地点在游戏《堡垒之夜》(Fortnite)中。全球上千万的玩家,使用自己的游戏角色盛装出席。演出中,一系列的小游戏贯穿了主线,最后粉丝在绚丽的场景中和“Ariana”一对…

42、Spring AMQP DirectExchange

1、DirectExchange 2、案例 3、通过配置类实现 1、配置类 2、Publisher 3、Consumer 4、测试结果 4、通过注解实现 1、Consumer添加Linstener 2、Publisher 执行测试方法 3、测试结果 4、总结分析 学习了DirectExchange之后&#xff0c;发现与FanoutExchange有些许差异&#…

腾讯程序员的手码K8S+Jenkins笔记

最近在GitHub上认识一个腾讯大佬&#xff0c;从他手上得到一份K8SJenkins笔记&#xff0c;好东西还是要共享的&#xff01;希望这份笔记能让你技术更上一层&#xff01; 从理论到实战深入K8S 学习K8S的学习笔记很少有比较齐全的&#xff0c;而这份K8S学习笔记&#xff0c;尽量…

谈了这么久的无代码到底是什么?

尽管我们一直在谈论关于“无代码”的方方面面&#xff0c;但却并没有专门谈及“无代码”是什么。不仅许多刚听到这个名词的人会感到疑惑&#xff0c;就算熟悉这一名词的可能也无法给出一个准确的定义&#xff0c;又或者常常将其与另一个相近的名词“低代码”弄混。 今天我们专…

Revit插件实现【快速扣减】的两种操作

翻模的过程中&#xff0c;我们在处理细节问题的时候&#xff0c;如果只是两个构件之间的重合&#xff0c;我们可以利用“连接”命令进行扣减。而如果是想让多个构件进行扣减操作&#xff0c;而不是整层或者整栋楼都按照同样的扣减顺序进行的话&#xff0c;我们可以利用橄榄山和…

Windows安全日志分析

Windows安全日志分析 0x00 引言 在应急响应初步阶段&#xff0c;我们会对系统日志、中间件安全日志、恶意文件等进行收集。接下来便是要进一步对这些文件进行分析&#xff1a;对恶意文件逆向、日志文件分析、梳理入侵时间线和入侵路径等。本文主要对Windows安全日志进行举例分…

Mybatis整合达梦数据库

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;国产数据库-达梦数据库&#xff08;主要讲一些达梦数据库相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下SpringBoot整合Mybatis与达梦数据库&#xff0c;就是简单…

全网最详细|Oracle12C安装和卸载图文教程

一. 数据库的引入 以前将数据用变量、数组、对象存在内存&#xff0c;而内存只能短暂存储数据。如果我们想长久存数据用文件将数据存在磁盘上&#xff0c;不方便存取和管理数据&#xff0c;因此可以使用数据库来存数据。 二. 数据库基础概念 2.1数据库(database,简称DB) 以…

QRegExpValidator(正则验证器)

QRegExpValidator QRegExpValidator 使用正则表达式 &#xff08;regexp&#xff09; 来确定输入字符串是可接受、中间还是无效的。正则表达式可以在构建 QRegExpValidator 时提供&#xff0c;也可以在以后提供。 构造函数&#xff1a; ​ 常用的函数&#xff1a; setRegE…

在Windows上使用Docker搭建ChirpStack私有LoRa服务端

在Windows上使用Docker容器部署ChirpStack服务&#xff0c;首先若要在Windows 10或11 上运行容器&#xff0c;需要以下条件&#xff1a; 一个运行 Windows 10 或 11 专业版或企业版的物理计算机系统。Hyper-V 功能已启用。 Windows下的Docker安装 1.Docker为Windows提供了一款…

如何设计一个支撑数亿用户的系统

要设计出一套能支撑几十亿人的系统是很困难的。对于软件架构师来说&#xff0c;这一直是一项很大的挑战&#xff0c;但是&#xff0c;从现在开始&#xff0c;看完我的文章&#xff0c;你就会觉得容易很多了。 下面是我在本文中提到的几个话题&#xff1a; 从最简单的开始&…

关于大数据测试,你一定要试试python的fake库

一.背景 对于大数据相关项目的测试&#xff0c;往往需要大量的测试数据&#xff0c;而场内测试时很难获取那么大批量的真实数据&#xff0c;这个时候需要测试人员能够快速模拟出符合要求的测试数据。这个时候要是有一个自动化工具或测试脚本简直不要太爽~ 二.问题引入 对于互…

【Ajax进阶】跨域和JSONP的学习

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 ajax学习 文章目录了解同源策略和跨域  同源策略    什么是同源    什么是同源策略跨域    什么是…

python基础07——函数,想重复使用自己的代码就写个函数吧

目录1 函数定义与调用1.1 自定义模块函数1.2 函数的返回值 return2 参数的传递方式2.1 位置参数2.2 默认值参数2.3 关键参数2.4 可变长度参数(收集和分配参数)2.4.1 收集位置参数2.4.2 分配位置参数,函数参数的序列解包2.4.3 收集关键字参数2.4.4 分配关键字参数3 变量的作用域…

腾讯云GPU云服务器在选择实例配置时应该注意哪些事项?

腾讯云异构计算服务器搭载GPU、FPGA等异构硬件&#xff0c;具有实时高速的并行计算和浮点计算能力&#xff0c;适合于深度学习、科学计算、视频编解码和图形工作站等高性能应用&#xff0c;下面分享腾讯云NVIDIA GPU云服务器配置CPU内存性能注意事项&#xff1a; NVIDIA GPU云…

26、Stream流式计算,链式编程

Stream流式计算&#xff0c;链式编程 什么是stream流式计算&#xff1f; 大数据包括&#xff08;存储计算&#xff09; 集合&#xff0c;mysql这些是用来存储的&#xff0c;而计算是用stream流式的 题目案例&#xff1a;只用一行代码&#xff0c;完成计算这些操作 操作&…

软件工程师备考1-2章(续)

一:差错控制 (1)奇偶检验 什么意思呢? 如果我们用 奇校验,就是保证传输过来的数据中的1是奇数,如果不是奇数那么说明传输错误。 (所以会增加一位,保证正确的数据的总的1一定是奇数) (2)海明码 什么是海明距离? 两个码字,例如 0 1 0 0 和 0 0 1 0 可以看到这…