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;@Overridepublic 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);@Overrideprotected 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 {@Overridepublic 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.mzph.cn/news/233628.shtml

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

相关文章

【3D数据读取】利用JAVA读取GLB(GLTF)文件数据

了解GLB和GLTF&#xff1a; GLB和GLTF是用于共享3D数据的标准化文件格式。GLB是GLTF的二进制格式&#xff0c;而GLTF基于JSON&#xff0c;一种基于文本的数据格式。 GLB文件&#xff1a; 由一个头部和一个二进制数据块组成。头部包含文件的元数据&#xff0c;例如文件版本、文件…

【C语言】数据结构——链式二叉树实例探究

&#x1f497;个人主页&#x1f497; ⭐个人专栏——数据结构学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 导读&#xff1a; 我们在前面学习了单链表&#xff0c;顺序表&#xff0c;栈和队列&#xff0c;小堆。 今天我们来学习链式二叉…

MATLAB 主成分分析PCA拟合平面点云 (42)

MATLAB 主成分分析PCA拟合平面点云 (42) 一、算法介绍二、算法实现一、算法介绍 主成分分析(Principal Component Analysis,PCA)是一种常用的数据降维和特征提取技术。它的主要思想是通过线性变换将数据投影到一个新的坐标系,使得在新的坐标系中数据的方差最大化。在3D点…

java读取含有合并单元格的Excel

java读取含有合并单元格的Excel Excel如下&#xff1a; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*;import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.…

springboot学习笔记(二)

1.Spring 和SpringBoot区别 2.Web开发入门 3.MVC模型 4.RequestMapping用法 5.RESTful 1.Spring 和SpringBoot区别 参考&#xff1a; 大家都懂Spring和SpringBoot的区别吗&#xff1f; - 知乎 https://www.zhihu.com/question/598494506/answer/3018702101 在学习了Spri…

Opencv实验合集——实验四:图片融合

1.概念 图像融合是将两个或多个图像结合在一起&#xff0c;创建一个新的图像的过程。这个过程的目标通常是通过合并图像的信息来获得比单个图像更全面、更有信息量的结果。图像融合可以在许多领域中应用&#xff0c;包括计算机视觉、遥感、医学图像处理等。 融合的方法有很多…

Docker 核心技术

Docker 定义&#xff1a;于 Linux 内核的 Cgroup&#xff0c;Namespace&#xff0c;以及 Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;属于操作系统层面的虚拟化技术&#xff0c;由于隔离的进程独立于宿主和其它的隔离的进程&#xff0c;因此也称其为容器Docke…

系统设计架构——互联网案例

Netflix 的技术栈 移动和网络:Netflix 采用 Swift 和 Kotlin 来构建原生移动应用。对于其 Web 应用程序,它使用 React。 前端/服务器通信:Netflix 使用 GraphQL。 后端服务:Netflix 依赖 ZUUL、Eureka、Spring Boot 框架和其他技术。 数据库:Netflix 使用 EV 缓存、Cas…

关于“Python”的核心知识点整理大全25

目录 10.3.4 else 代码块、 10.3.5 处理 FileNotFoundError 异常 alice.py 在这个示例中&#xff0c;try代码块引发FileNotFoundError异常&#xff0c;因此Python找出与该错误匹配的 except代码块&#xff0c;并运行其中的代码。最终的结果是显示一条友好的错误消息&#x…

云计算:FusionCompute 通过 FreeNAS 添加SAN存储

目录 一、实验 1.环境准备 2.FusionCompute添加CNA 3.在存储中创建LUN资源映射给CNA节点 3.添加存储资源关联CNA主机节点 4.扫描存储资源 5.将存储设备添加为数据存储 二、问题 1.FusionCompute中存储如何分类 2.存储资源与存储设备有何区别 3.FusionCompute支持哪些…

线上环境如何正确配置 Django 的 DEBUG?

Author&#xff1a;rab Django Version&#xff1a;3.2 Python Version&#xff1a;3.9 目录 前言一、DEBUG True二、DEBUG False三、页面异常解决总结 前言 由于最近在学习 Django 的知识&#xff0c;于是尝试开发了一套 Blog 系统&#xff0c;在本地测试时是页面显示没问题…

零刻EQ12 N100 2.5G双网口 All In One新手教程

零刻EQ12 N100 2.5G双网口 All In One新手教程 前言1.硬件配置2.准备工作2.1. ESXI8.0U2镜像2.2. Rufus磁盘工具下载2.3. ikuai镜像下载2.4. StarWindConverter虚拟磁盘格式转换工具下载2.5. OpenWrt镜像下载2.6. 黑群晖RR引导镜像下载(DSM7.2)2.7. 需要准备的硬件2.8. 格式化需…

【ArcGIS微课1000例】0081:ArcGIS指北针乱码解决方案

问题描述&#xff1a; ArcGIS软件在作图模式下插入指北针&#xff0c;出现指北针乱码&#xff0c;如下图所示&#xff1a; 问题解决 下载并安装字体&#xff08;配套实验数据包0081.rar中获取&#xff09;即可解决该问题。 正常的指北针选择器&#xff1a; 专栏介绍&#xff…

【复杂网络分析与可视化】——通过CSV文件导入Gephi进行社交网络可视化

目录 一、Gephi介绍 二、导入CSV文件构建网络 三、图片输出 一、Gephi介绍 Gephi具有强大的网络分析功能&#xff0c;可以进行各种网络度量&#xff0c;如度中心性、接近中心性、介数中心性等。它还支持社区检测算法&#xff0c;可以帮助用户发现网络中的群组和社区结构。此…

设计模式——0前言目录

1 设计模式介绍 应当站在产品经理的角度来学习设计模式 是软件设计中常见问题的典型解决方案&#xff0c;可用于解决代码中反复出现的设计问题 学习效果一般的原因在于自己没有站在产品经理的角度学习&#xff0c;仅仅是为了学习怎么实现&#xff0c;用什么算法实现。 分类&…

pycharm运行正常,但命令行执行提示module不存在的多种解决方式

问题描述 在执行某个测试模块时出现提示&#xff0c;显示自定义模块data不存在&#xff0c;但是在PyCharm下运行正常。错误信息如下&#xff1a; Traceback (most recent call last):File "/run/channelnterface-autocase/testcases/test_chanel_detail.py", line 2…

JavaSE学习笔记 Day20

JavaSE学习笔记 Day20 个人整理非商业用途&#xff0c;欢迎探讨与指正&#xff01;&#xff01; 上一篇 文章目录 JavaSE学习笔记 Day20十七、数据结构与算法17.1算法17.1.1冒泡排序17.1.2选择排序17.1.3插入排序17.1.4三个排序的区别 17.2顺序表17.2.1顺序表代码实现17.2.2顺…

蜘点云原生之 KubeSphere 落地实践过程

作者&#xff1a;池晓东&#xff0c;蜘点商业网络服务有限公司技术总监&#xff0c;从事软件开发设计 10 多年&#xff0c;喜欢研究各类新技术&#xff0c;分享技术。 来源&#xff1a;本文由 11 月 25 日广州站 meetup 中讲师池晓东整理&#xff0c;整理于该活动中池老师所分享…

npm login报错:Public registration is not allowed

npm login报错:Public registration is not allowed 1.出现场景2.解决 1.出现场景 npm login登录时,出现 2.解决 将自己的npm镜像源改为npm的https://registry.npmjs.org/这个&#xff0c;解决&#xff01;

Android Studio 显示前进后退按钮

在写代码的过程中我们经常需要快速定位到先前或者往后的代码位置&#xff0c;可以使用Alt左右箭头 但是新安装的Android Studio工具栏上是没有显示左右箭头的工具按钮的&#xff0c;需要我们设置将Toolbar显示出来 View-Appearance-Toolbar 勾选即可 显示后