深入了解tomcat线程池

news2024/10/3 4:39:51

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/7274.html

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

相关文章

Vue3 - 不再支持 IE11,到底为什么?

前言 咱们的 Vue2 目前仍然支持 IE11&#xff0c;但是到了 Vue3 这里&#xff0c;直接被抛弃了。 IE 浏览器可以说是早期前端开发的噩梦&#xff0c;现在还充斥的大量兼容 IE 浏览器的代码&#xff0c;你可以在网上看到很多类似的信息。 IE 浏览器下 float 布局错乱。IE 浏览器…

商务呈现之沟通管理-上

一、前言 课程目标及适用人群课程目标:商务/项目的目标达成,任务推动,良好的商务呈现 现实的困扰我们商务活动中是否有遇到以下情况: (1)需求老是变 理解不一致细节不清晰(2)CR很难谈 需求基线不清晰没有利用好"交换"(3)原地打转 事项推进缓慢几个月还在讨…

相似度系列-6:单维度方法:Evaluating Coherence in Dialogue Systems using Entailment

Evaluating Coherence in Dialogue Systems using Entailment coherence 英文中意味着连贯性、条理性。 这篇文章是面向对话应用的&#xff0c;更加关注于对话中上下位的连贯性。1. 直接转换为 NLI问题&#xff0c;premise-hypothesis问题。——2. 数据集是自己构造的。——数…

一文带你了解【抽象类和接口】

1. 抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是并不是所有类都是用来描绘对象的。如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 举个简单的例子 上图中&#xff0c;三角形&#xff0…

构建一个商业智能 BI 分析平台,公司CIO应该重点关注什么?

企业级商业智能 BI 分析平台的构建是一个系统型的工程&#xff0c;涉及业务分析需求的把控、各类数据资源的整合清洗、数据仓库的架构设计、可视化分析报表逻辑设计、IT 部门与业务部门的工作边界划分与配合等等居多环节。 每一个环节的重要性都不容忽视&#xff0c;第一是业务…

(算法设计与分析)第三章动态规划-第二节:动态规划之背包类型问题

文章目录一&#xff1a;01背包问题&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;解题思路&#xff08;3&#xff09;完整代码二&#xff1a;分割等和子集&#xff08;01背包变形&#xff09;&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;解题思路…

Java:Jar包反编译,解压和压缩

1、简述 JAR 文件就是 Java Archive &#xff08; Java 档案文件&#xff09;&#xff0c;它是 Java 的一种文档格式。 JAR 文件非常类似 ZIP 文件。准确的说&#xff0c;它就是 ZIP 文件&#xff0c;所以叫它文件包。JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中&a…

蓝桥杯必备算法分享——差分算法

AcWing—差分算法 文章目录AcWing---差分算法一、什么是差分&#xff1f;二、差分的作用三、一维差分模板四、二维差分五、二维差分构造方法图示&#xff1a;六、二维差分矩阵模板总结差分算法是前缀和算法的逆运算。两者可以对比着学习&#xff1a; 一、什么是差分&#xff1…

旭日图超越了传统的饼图和圆环图,能表达清晰的多层级和归属关系

“旭日图是什么&#xff1f;用来干什么&#xff1f;” “没听说过旭日图......” “旭日图不就是多层饼图嘛......” 鉴于很多人对旭日图都不太了解&#xff0c;那今天我们就用平台的旭日图为大家讲解。旭日图看似低调简单&#xff0c;却一点都不简单&#xff01; 旭日图由多…

关于pool.apply_async的学习【参数问题】

一、学习 参考&#xff1a;(1条消息) python pool.apply_async调用 参数为dataset的函数 不执行问题解决_嗯嗲和滴的博客-CSDN博客_pool.apply_async参数 一个参数的情况 一定要加逗号 在只有一个参数要传时 需要写成列表/元组的形式&#xff1a; task_fun.apply_async(args[v…

从零开始学前端:垃圾回收机制,闭包,案例 --- 今天你学习了吗?(JS:Day11)

从零开始学前端&#xff1a;程序猿小白也可以完全掌握&#xff01;—今天你学习了吗&#xff1f;&#xff08;JS&#xff09; 复习&#xff1a;从零开始学前端&#xff1a;作用域、执行顺序 — 今天你学习了吗&#xff1f;&#xff08;JS&#xff1a;Day10&#xff09; 文章目…

网络编程——封装和分用(图解)

一、什么是封装 &#x1f48c;&#x1f48c;&#x1f48c;网络编程中的封装&#xff0c;并不是Java面向对象思想里的封装&#xff0c;继承&#xff0c;多态的封装.它是应用程序通过TCP协议传送数据时&#xff0c;每一次进行包装送入网络中&#xff0c;像极了发快递时的你. 思考…

FFplay文档解读-51-多媒体资源

33. 多媒体资源 以下是当前可用多媒体源的说明。 33.1 amovie 这与电影源相同&#xff0c;但默认情况下会选择音频流。 33.2 movie 从电影容器中读取音频和/或视频流。 它接受以下参数&#xff1a; filename要读取的资源的名称&#xff08;不一定是文件;它也可以是通过某…

[附源码]java毕业设计基于web的健康信息管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Oracle Primavera Unifier文档管理器(Document Manager)

目录 引言 介绍 引言 在 Oracle Primavera Unifier 中&#xff0c;文档管理器维护项目的所有文件和文档。每个项目/外壳都包含自己的文档管理器&#xff0c;另一个文档管理器驻留在公司级别。管理员确保员工和项目/shell 成员始终使用最新版本的文档&#xff0c;并使访问和分…

热成像仪在LED产品的应用

热成像仪对LED产品的检测 LED产品检测 LED作为取代传统照明工具(如白炽灯、卤素灯等)的新型光源&#xff0c;但其散热效果严重影响LED的实际使用寿命&#xff0c;散热工艺成为LED应用和发展的关键因素&#xff0c;红外热像仪可以进行LED温度检测&#xff0c;帮助验证散热工艺。…

如何对珍贵水生物标本提供三维重建,数字化技术助力长江大保护...

近日&#xff0c;由武汉白鱀豚保护基金会发起的“看见长江的美好”系列长江大保护行动中&#xff0c;英特尔 Evo X BMW i 打造了“移动数字探索工作室”&#xff0c;携手【云端地球】团队来到了拥有百年生命印记的地方&#xff0c;这里收藏着包括白鲟、白鱀豚、中华鲟、长江江豚…

密码在智能汽车数据安全领域的应用研究报告

开放隐私计算 开放隐私计算 开放隐私计算OpenMPC是国内第一个且影响力最大的隐私计算开放社区。社区秉承开放共享的精神&#xff0c;专注于隐私计算行业的研究与布道。社区致力于隐私计算技术的传播&#xff0c;愿成为中国 “隐私计算最后一公里的服务区”。 177篇原创内容 …

HTTP状态码301和302的区别

简介 在HTTP状态码中&#xff0c;3XX表示重定向&#xff0c;指网页发生了转移&#xff0c;重定向到目标地址中。 301&#xff1a;表示永久性转移 &#xff08;Permanently Moved&#xff09; 302&#xff1a;表示临时性转移&#xff08;Temporarily Moved&#xff09; 说明…

java基础新

目录 集合总结 ​编辑Java异常体系 常见的运行时异常&#xff1f; Object类有哪些方法 ​编辑权限修饰符 分布式锁 Redis和Zookeeper实现分布式所哪个效率高 分布式事务 事务失效的8种原因 TCC 如果出现网络连不通怎么办&#xff1f; CAP理论 CAP有哪些组合方式&…