jdk 线程池与 tomcat 线程池对比

news2024/11/17 7:47:44

一、线程池的作用

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 是更通用和灵活性更高一些

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

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

相关文章

2023_Spark_实验三十:测试Flume到Kafka

实验目的&#xff1a;测试Flume采集数据发送到Kafka 实验方法&#xff1a;通过centos7集群测试&#xff0c;将flume采集的数据放到kafka中 实验步骤&#xff1a; 一、 kafka可视化工具介绍 Kafka Tool是一个用于管理和使用Apache Kafka集群的GUI应用程序。 Kafka Tool提供了…

《点云处理》 点云去噪

前言 通常从传感器&#xff08;3D相机、雷达&#xff09;中获取到的点云存在噪点&#xff08;杂点、离群点、孤岛点等各种叫法&#xff09;。噪点产生的原因有不同&#xff0c;可能是扫描到了不想要扫描的物体&#xff0c;可能是待测工件表面反光形成的&#xff0c;也可能是相…

原子学习笔记2——输入设备应用编程

一、输入类设备介绍 1、输入设备 常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等&#xff0c;用户通过输入设备与系统进行交互。 2、input子系统 常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等&#xff0c;用户通过输入设备与系统进行交互。 基于…

docker 在线安装redis

1、远程仓库拉取redis镜像&#xff0c; docker pull redis&#xff0c;默认拉取最新版本 2、在本地宿主机文件夹下创建相关目录文件&#xff0c;供容器卷使用&#xff0c;创建 /usr/local/data/redisdocker/data 文件夹&#xff0c;准备一个纯净版 redis.conf 配置文件 &#x…

【Linux】ip命令使用

ip命令 用于管理与配置网络接口和路由表。 ip命令的安装 ip 命令来自 iproute2 软件包&#xff0c;在 CentOS 7 中默认已安装。 yum install -y iproute 语法 ip [ OPTIONS ] OBJECT { COMMAND | help }ip [ -force ] -batch filename选项及作用 执行令 &#xff1a; ip …

el-form与el-upload结合上传带附件的表单数据(后端篇)

1.写在之前 本文采用Spring Boot MinIO MySQLMybatis Plus技术栈&#xff0c;参考ruoyi-vue-pro项目。 前端实现请看本篇文章el-form与el-upload结合上传带附件的表单数据&#xff08;前端篇&#xff09;-CSDN博客。 2.需求描述 在OA办公系统中&#xff0c;流程表单申请人…

【SQL】根据年月,查询月份中每一天的数据量

传入YYYY-MM-01&#xff0c;查询这个月中每一天的数据量&#xff0c;没有数据的天数用0表示 WITH RECURSIVE DateRange AS (SELECT :startDate AS DateUNION ALLSELECT DATE_ADD(Date, INTERVAL 1 DAY) FROM DateRangeWHERE Date < LAST_DAY(:startDate) ) SELECTdr.Date,CO…

从 MySQL 到 DolphinDB,Debezium + Kafka 数据同步实战

Debezium 是一个开源的分布式平台&#xff0c;用于实时捕获和发布数据库更改事件。它可以将关系型数据库&#xff08;如 MySQL、PostgreSQL、Oracle 等&#xff09;的变更事件转化为可观察的流数据&#xff0c;以供其他应用程序实时消费和处理。本文中我们将采用 Debezium 与 K…

单总线cpu设计(包含定长指令周期和变长指令周期和现代时序设计)

来都来了点个赞收藏一下再走呗~~~&#x1f339;&#x1f339;&#x1f339;&#x1f339;&#x1f339; 一、定长指令周期cpu设计 第1关&#xff1a;MIPS指令译码器设计 此实验就是只需要知道mips知道操作码op对应的值是什么就可以了&#xff0c;下面给出实验中用到的mips指令…

大型语言模型:RoBERTa — 一种稳健优化的 BERT 方法

slavahead 一、介绍 BERT模型的出现BERT模型带来了NLP的重大进展。 BERT 的架构源自 Transformer&#xff0c;它在各种下游任务上取得了最先进的结果&#xff1a;语言建模、下一句预测、问答、NER标记等。 尽管 BERT 性能出色&#xff0c;研究人员仍在继续尝试其配置&#xff0…

JNDI注入Log4jFastJson白盒审计不回显处理

目录 0x00 前言 0x01 Maven 仓库及配置 0x02 JNDI 注入简介 0x03 Java-第三方组件-Log4J&JNDI 0x04 Java-第三方组件-FastJson&反射 0x05 白盒审计 - FastJson 0x06 白盒审计 - Log4j 0x07 不回显的处理方法 0x00 前言 希望和各位大佬一起学习&#xff0c;如果…

RK3399平台开发系列讲解(内核入门篇)网络协议的分层

🚀返回专栏总目录 文章目录 一、应用层二、传输层三、网络层四、数据链路层(Data Link Layer)五、物理层沉淀、分享、成长,让自己和他人都能有所收获!😄 📢对于多数的应用和用户而言,使用互联网的一个基本要求就是数据可以无损地到达。用户通过应用进行网络通信࿰

Android的组件、布局学习

介绍 公司组织架构调整&#xff0c;项目组需要承接其他项目组的android项目&#xff0c;负责维护和开发新需求&#xff0c;故学习下基础语法和项目开发。 组件学习 Toolbarheader布局部分 就是app最顶部的部分 他的显示与否&#xff0c;是与F:\androidProject\android_lear…

LVGL 显示图片

LVGL 显示图片 LVGL显示图片1. 显示图片文件2. 显示C数组格式3. 显示RAM中的图像文件4. 图像符号显示5. 显示GIF动画LVGL显示图片代码分析 LVGL显示图片 lvgl 8.3版本默认支持PNG,BMP,JPG,SJPG和GIF动图等格式的图片显示&#xff1b; 需要在lv_conf.h配置文件里使能对应图片的…

【网络安全】—Shell编程入门(1)

文章目录 基础变量概念介绍特殊变量进阶数值计算实践条件测试比较条件判断语句流程控制语句循环语句应用 Shell 是 Unix/Linux 操作系统下的一种命令行解释器&#xff0c;它接收用户输入的命令然后调用相应的程序。我们可以通过 Shell 脚本来自动执行一系列的命令。接下来&…

基于python的leetcode算法介绍之递归

文章目录 零 算法介绍一 简单示例 辗转相除法Leetcode例题与思路[509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/)解题思路&#xff1a;题解&#xff1a; [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)解题思路&#xff1a;题解&…

最新AI创作系统ChatGPT系统源码+DALL-E3文生图+AI绘画+GPT语音对话功能

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

计算机网络 网络层下 | IPv6 路由选择协议,P多播,虚拟专用网络VPN,MPLS多协议标签

文章目录 5 IPv65.1 组成5.2 IPv6地址5.3 从IPv4向IPv6过渡5.3.1 双协议栈5.3.2 隧道技术 6 因特网的路由选择协议6.1 内部网关协议RIP6.2 内部网关协议 OSPF基本特点 6.3 外部网关协议 BGP6.3.1 路由选择 6.4 路由器组成6.4.1 基本了解6.4.2 结构 7 IP多播7.1 硬件多播7.2 IP多…

vp与vs联合开发-通过FrameGrabber连接相机

添加控件 1.CogRecordDisplay 控件 用于显示图像 初始化相机对象方法 //启动窗体时 调用初始化相机方法 //封装相机关闭方法 //窗体关闭时 调用相机关闭方法 拍照 设置采图事件 // 保存图像 设置曝光按钮事件 1.可变参数

C# pictureBox显示一张图片,我想先释放这个图片以免占用无法修改,(旋转)改完再显示这张图片

效果 public static bool RotateFlip(MyDel Log, string fileName){try{string tempPath Path.GetTempFileName();using (Bitmap bmp new Bitmap(fileName)){float resolution 600; //x,y必须为这个数 误差小于-1bmp.RotateFlip(RotateFlipType.Rotate90FlipNone);bmp.Save(…