jdk 线程池与 tomcat 线程池对比

一、线程池的作用

1. 提高性能:线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有空间,同时也会一比一的创建一个内核线程,在线程销毁时需要回收这些系统资源。频繁地创建和销毁线程会大大浪费系统资源,这时候就需要线程池来管理线程,提高线程的复用

2. 控制并发度:限制同时执行的线程数量,通过控制并发度来避免资源过度占用和系统过载。

3. 任务排队:提供任务队列,可以将所有待执行的任务进行排队,保证任务按照一定的顺序执行,避免因为线程不足而导致任务丢失

二、java 自带线程池 ThreadPoolExecutor

Java JUC下提供了一套 Executor 框架,主要支持以下几种线程池的创建

a. FixedThreadPool(固定大小线程池):维护固定数量的线程,任务提交后会立即执行。如果所有线程都被占用,新任务会被放入任务队列中等待。适用于并发任务数固定且较小的情况。

b. CachedThreadPool(缓存线程池):线程池大小不固定,根据任务量动态创建和回收线程。如果当前有空闲线程,则会直接使用,如果没有则会创建新的线程。适用于并发任务数较大或者任务执行时间较短的情况。

c. SingleThreadPool(单线程池):线程池中只有一个线程,所有的任务按照顺序执行。适用于保证任务执行顺序的场景,如任务间有依赖关系或需要按照提交顺序执行。

d. ScheduledThreadPool(定时线程池):用于定时执行任务和周期性执行任务。可以设置线程数量和延迟时间来执行任务

不推荐使用 Executors 工厂模式创建上述四种线程池,缺少很多线程池的参数设置且默认参数并不合理,容易出现性能问题或者资源浪费。推荐使用 ThreadPoolExecutor类 (类结构图如下) 手动创建线程池,可以自定义线程池的大小、任务队列、拒绝策略以及其他参数。这样可以根据具体的业务需求和系统资源状况来优化线程池的性能和稳定性。

ThreadPoolExecutor 核心参数
  • corePoolSize(核心线程数):表示线程池中保持活动状态的线程数量。在没有任务执行时,核心线程也会一直存在。当有新任务提交时,线程池会优先创建核心线程来处理任务。
  • maximumPoolSize(最大线程数):表示线程池中允许存在的最大线程数。当线程池中的线程数达到最大线程数并且任务队列已满时,新提交的任务会触发拒绝策略。
  • keepAliveTime(线程空闲时间):表示当线程池中的线程数量超过核心线程数时,空闲线程在被终止之前要等待新任务的时间。线程空闲时间的单位由TimeUnit参数指定。
  • unit(时间单位):用于指定keepAliveTime的时间单位,可以是秒、毫秒、微秒等。
  • workQueue(任务队列):用于存储待执行的任务的队列。线程池中的线程会从任务队列中取出任务进行执行。
  • ThreadPoolExecutor提供了多种实现供选择,如ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等。
  • threadFactory(线程工厂):用于创建新线程的工厂,默认使用Executors.defaultThreadFactory()。 handler(拒绝策略):当线程池无法处理新提交的任

      如果 corePoolSize 长时间无效占用线程数量,可通过 allowCoreThreadTimeOut 设置项要求线程池:将包括“核心线程”在内的,没有任务分配的所有线程,在等待 keepAliveTime 时间后全部回收掉。

线程池的线程分配流程

三、tomcat 线程池 StandardThreadExecutor 

        不同于 jdk 自带的线程池,tomcat 应用的场景基本都是IO密集型请求,即系统请求非常消耗CPU的占比比较低,所以tomcat在设计线程池的时候,重新设计了线程池分配原则,请求进来时会优先创建并分配线程而不是进入等待队列

设计点1: 增加继承 LinkedBlockingQueue 的 TaskQueue

    private transient volatile ThreadPoolExecutor parent = null;

    @Override
    public boolean offer(Runnable o) {
        // 如果线程池为空,直接入队列
        if (parent==null) return super.offer(o);
        // 当前线程池线程数 = 最大线程池数,进入队列等待
        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;
        // 其他条件进去等待队列
        return super.offer(o);
    }

你是不是会疑惑,已提交并执行中的任务数 <= 当前线程池线程数,为什么是进入队列等待 ?

首先看下 submittedCount 是干什么用的,跟踪代码发现 tomcat 自己也写了一个  org.apache.tomcat.util.threads.ThreadPoolExecutor ,submittedCount 的主要职责是记录已提交的任务数,调用 execute 时 +1 ,afterExecute 执行结束时 - 1, 即该条件成立下进入队列的任务很快就会被执行,举个具体的栗子看下,即在线程运行资源不紧张的情况下,可以有效地控制线程池的负载,避免过多的线程创建和销毁,提高线程池的性能和资源利用率

任务线程数
a (即将结束)1

b(进行中)

2
c (新进入)队列中等待
d (新进入)进入下一个条件继续创建线程

设计点2: 提供自己的 org.apache.tomcat.util.threads.ThreadPoolExecutor 

public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
    

    /**
     * The number of tasks submitted but not yet finished. This includes tasks
     * in the queue and tasks that have been handed to a worker thread but the
     * latter did not start executing the task yet.
     * This number is always greater or equal to {@link #getActiveCount()}.
     */
    private final AtomicInteger submittedCount = new AtomicInteger(0);

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        submittedCount.decrementAndGet();

        if (t == null) {
            stopCurrentThreadIfNeeded();
        }
    }

    public int getSubmittedCount() {
        return submittedCount.get();
    }

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the <code>Executor</code> implementation.
     * If no threads are available, it will be added to the work queue.
     * If the work queue is full, the system will wait for the specified
     * time and it throw a RejectedExecutionException if the queue is still
     * full after that.
     *
     * @param command the runnable task
     * @param timeout A timeout for the completion of the task
     * @param unit The timeout time unit
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution - the queue is full
     * @throws NullPointerException if command or unit is null
     */
    public void execute(Runnable command, long timeout, TimeUnit unit) {
        submittedCount.incrementAndGet();
        try {
            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;
            }

        }
    }

    private static class RejectHandler implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r,
                java.util.concurrent.ThreadPoolExecutor executor) {
            throw new RejectedExecutionException();
        }

    }
}

TaskQueue 与 ThreadPoolExecutor 配合,修改线程分配策略

public class ThreadPoolExecutor extends AbstractExecutorService {

     public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        // ctl 控制线程池的状态和活动线程的数量(低29位:线程池数量、高3位:线程池状态)
        int c = ctl.get();
        // 活动线程数 < 核心线程数, 创建线程
        if (workerCountOf(c) < corePoolSize) {
            // 尝试添加一个新的工作线程来执行任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 线程池处于运行状态,并且任务成功添加到工作队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 线程池非运行状态并且移除任务成功,走拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 没有活动线程,添加一个新的工作线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 线程池处于运行状态或入队不成功(taskQueue返回false),尝试创建新线程
        else if (!addWorker(command, false))
            // 创建线程失败走拒绝策略
            reject(command);
    }
}

四、总结

对比 Tomcat  线程池和 JDK 线程池,一个是线程数未达到最大线程数之前,优先创建线程执行任务,另一个是队列未满,优先让任务排队,总体而言tomcat线程池更适用于 IO 密集型应用场景,而对于CPU密集型任务,ThreadPoolExecutor 是更通用和灵活性更高一些

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

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

相关文章

进制间通讯 消息队列

实现AB进程对话。 a. A进程发送一句话后&#xff0c;B进程接收到打印。然后B进程发送一句话&#xff0c; A进程接收后打印 b.重复上述步骤。直到AB接收或者发送完quit后,结束AB进程

【漏洞复现】CVE-2023-6848 kodbox远程命令执行

漏洞描述 kodbox 是一个网络文件管理器。它也是一个网页代码编辑器,允许您直接在网页浏览器中开发网站。您可以在基于 Linux、Windows 或 Mac 的平台上在线或本地运行 kodbox。唯一的要求是要有 PHP 5及以上。 kalcaddle kodbox 中发现漏洞,最高版本为 1.48。它已被宣布为关…

记录下系统异常到数据库中,方便查找定位,省去翻日志的麻烦。

将异常消息&#xff0c;产生时间&#xff0c;帧信息&#xff0c;操作信息等存入表中&#xff0c;方便查阅修改。适合小系统。大系统没试过。 在异常通知对象HandlerExceptionResolver解析完异常后&#xff0c;构建一个异常相关信息实体。存入表中。代码如下&#xff1a; publ…

[23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians

[paper | proj] 给定FLAME&#xff0c;基于每个三角面片中心初始化一个3D Gaussian&#xff08;3DGS&#xff09;&#xff1b;当FLAME mesh被驱动时&#xff0c;3DGS根据它的父亲三角面片&#xff0c;做平移、旋转和缩放变化&#xff1b;3DGS可以视作mesh上的辐射场&#xff1…

英文表示数字_分支结构 C语言xdoj146

题目描述&#xff1a;输入一个0~9的一位整数&#xff0c;输出其对应的英文单词。要求用switch结构完成。 示例&#xff1a; 输入&#xff1a;0 输出&#xff1a;zero 输入&#xff1a;8 输出&#xff1a;eight #include <stdio.h>//英文表示数字_分支结构 int main() {in…

一招教你如何绕过OpenAI API key创建时需要手机号验证

一招教你如何绕过OpenAI API key创建时需要手机号验证 虽然现在 ChatGPT 注册门槛极大地降低。但是&#xff0c;如果你是开发者或者需要第三方应用接入ChatGPT&#xff0c;此时就需要获取一个 API key&#xff0c;然而你可能会发现在你在创建 key 的过程中需要进行手机号验证。…

【unity小技巧】使用三种方式实现瞄准瞄具放大变焦效果

最终效果对比 文章目录 最终效果对比前言第一种办法方法二1. 创建URP环境2. 配置 Universal Render Pipeline Asset3. 这里向我们新建一个无光的ShaderGraph4. 主图配置4. 新建材质&#xff0c;挂载5. 下面是shaderGraph 的连线图6. 新增脚本控制ObjectScreenPosition随着瞄准镜…

org.slf4j日志组件实现日志功能

slf4j 全称是Simple Logging Facade for Java。facade是一种设计模式。 slf4j 是一个抽象程度更高的日志组件&#xff0c;本身并不提供实际的日志功能。实际的日志功能是通过log4j等日志组件实现&#xff0c;而使用者只需要关心 slf4j 给出的API。 slf4j 仅仅是一个为Java程序提…

bootstap table表格, 获取当前点击的table元素在该行是第几个

背景 有这样一个需求, table表格中是统计数据, 要求点击每个单元格可实现导出统计的底层数据 数据都是可点击导出的, 思路 获取行bootstap 有个index参数, 所哟要获取当前行第几列, 要获取当前点击的table元素在其所在行中的位置&#xff08;即第几个&#xff09;&#xff…

《每天一分钟学习C语言·二》

1、当使用const关键字变量就无法修改可当常量来用。常量指针不能通过指针来改变变量的值&#xff0c;但可以通过其他引用来改变变量的值常量指针也可以指向其他变量地址&#xff0c;如 int a5,b6; const int *pt &a; *pt6; //错误 a6; //正确 pt&b; //正确指针常量指…

0057-基本数据类型和 String 类型的转换

文章目录 基本数据类型和 String 类型的转换注意事项 基本数据类型和 String 类型的转换 注意事项

Oracle的学习心得和知识总结(三十)| OLTP 应用程序的合成工作负载生成器Lauca论文翻译及学习

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

计算机网络:物理层(编码与调制)

今天又学会了一个知识&#xff0c;加油&#xff01; 目录 一、基带信号与宽带信号 1、基带信号 2、宽带信号 3、选择 4、关系 二、数字数据编码为数字信号 1、非归零编码【NRZ】 2、曼彻斯特编码 3、差分曼彻斯特编码 4、归零编码【RZ】 5、反向不归零编码【NRZI】 …

.NET 自定义中间件 判断是否存在 AllowAnonymousAttribute 特性 来判断是否需要身份验证

public Task InvokeAsync(HttpContext context){// 获取终点路由特性var endpointFeature context.Features.Get<IEndpointFeature>();// 获取是否定义了特性var attribute endpointFeature?.Endpoint?.Metadata?.GetMetadata<AllowAnonymousAttribute>();if …

giee 添加公匙 流程记录

一、安装 百度网盘CSDN4文件夹下&#xff0c;或者官网下载&#xff1a;https://git-scm.com/downloads 二、生成密钥 1.右击打开git bash 2.$ ssh-keygen -t rsa -C “个人邮箱地址”&#xff0c;按3个回车&#xff0c;密码为空。 3.在C:\Users{windows用户名}.ssh目录下得到…

G1506 小电流升压型LED驱动芯片

G1506 小电流升压型LED驱动芯片 概述 &#xff1a; G1506是一种专为以恒定电流来驱动白光LED而设计的升压型DC/DC变换器。该器件能利用一节锂离子电池来驱动两个、三个或四个串联的LED采用LED串联连接的方法可以提供相等的LED电流从而能获得均匀的亮度且无需镇流电阻器。G1506的…

查找Apple Watch的序列号有重要意思,主要有两种方法

如果你打算购买二手Apple Watch&#xff0c;你可能需要检查它的序列号或IMEI号&#xff0c;来确保可靠性。以下是如何从Apple Watch和iPhone中查找序列号。 在Apple Watch上查找序列号和IMEI 1、在Apple Watch上&#xff0c;按下手表表面的数字皇冠以打开应用程序网格或列表。…

Unity中Shader测试常用的UGUI功能简介

文章目录 前言一、锚点1、锚点快捷修改位置2、使用Anchor Presets快捷修改3、Anchor Presets界面按下 Shift 可以快捷修改锚点和中心点位置4、Anchor Presets界面按下 Alt 可以快捷修改锚点位置、UI对象位置 和 长宽大小 二、Canvas画布1、UGUI中 Transform 变成了 Rect Transf…

华清远见嵌入式学习——ARM——作业1

要求&#xff1a; 代码&#xff1a; mov r0,#0 用于加mov r1,#1 初始值mov r2,#101 终止值loop: cmp r1,r2addne r0,r0,r1addne r1,r1,#1bne loop 效果&#xff1a;

Python 全栈体系【四阶】(七)

第四章 机器学习 六、多项式回归 1. 什么是多项式回归 线性回归适用于数据呈线性分布的回归问题。如果数据样本呈明显非线性分布&#xff0c;线性回归模型就不再适用&#xff08;下图左&#xff09;&#xff0c;而采用多项式回归可能更好&#xff08;下图右&#xff09;。例…