【线程池】Tomcat线程池

server/2024/10/25 14:35:35/

版本:tomcat-embed-core-10.1.8.jar

前言

最近面试被问到 Tomcat 线程池,因为之前只看过 JDK 线程池,没啥头绪。在微服务横行的今天,确实还是有必要研究研究 Tomcat 的线程池

Tomcat 线程池和 JDK 线程池最大的不同就是它先把最大线程数用完,然后再提交任务到队列里面。强烈建议先弄懂 JDK 线程池原理再看本文,推荐阅读:深入理解线程池(ThreadPoolExecutor)——细致入微的讲源码。_if (workeradded) { t.start(); workerstarted = true-CSDN博客

Tomcat 线程池

先打开 tomcat 的 server.xml 看一下,发现 tomcat 支持配置最大和最小线程数:

    <!--The connectors can use a shared executor, you can define one or more named thread pools--><!--<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"maxThreads="150" minSpareThreads="4"/>-->

可配置的参数的中文说明如下:

图片

这里主要关注线程池实现类,默认为 org.apache.catalina.core.StandardThreadExecutor

org.apache.catalina.core.StandardThreadExecutor#startInternal

java">/*** Start the component and implement the requirements of* {@link org.apache.catalina.util.LifecycleBase#startInternal()}.** @exception LifecycleException if this component detects a fatal error that prevents this component from being used*/
@Override
protected void startInternal() throws LifecycleException {
​// 创建 Tomcat 自定义的任务队列taskqueue = new TaskQueue(maxQueueSize);// 创建线程工厂TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, getThreadPriority());// 创建线程池executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);executor.setThreadRenewalDelay(threadRenewalDelay);// 最关键的地方,没有这行代码,Tomcat 的线程池则会表现的和 JDK 的线程池一样taskqueue.setParent(executor);
​setState(LifecycleState.STARTING);
}

这段方法就是构建线程池的地方,关键参数如下:

图片

org.apache.tomcat.util.threads.ThreadPoolExecutor

java">public class ThreadPoolExecutor extends AbstractExecutorService {public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0) {throw new IllegalArgumentException();}if (workQueue == null || threadFactory == null || handler == null) {throw new NullPointerException();}this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;
​prestartAllCoreThreads();}
​/*** Starts all core threads, causing them to idly wait for work. This* overrides the default policy of starting core threads only when* new tasks are executed.** @return the number of threads started*/public int prestartAllCoreThreads() {int n = 0;while (addWorker(null, true)) {++n;}return n;}
}

tomcat 的线程池的名字和 jdk 线程池的名字一样,千万别搞混了。从构造函数可以看到,tomcat 线程池在创建完成后会调用 prestartAllCoreThreads 方法对所有核心线程做一次预热

线程池执行

java">private void executeInternal(Runnable command) {if (command == null) {throw new NullPointerException();}/** Proceed in 3 steps:** 1. If fewer than corePoolSize threads are running, try to* start a new thread with the given command as its first* task.  The call to addWorker atomically checks runState and* workerCount, and so prevents false alarms that would add* threads when it shouldn't, by returning false.** 2. If a task can be successfully queued, then we still need* to double-check whether we should have added a thread* (because existing ones died since last checking) or that* the pool shut down since entry into this method. So we* recheck state and if necessary roll back the enqueuing if* stopped, or start a new thread if there are none.** 3. If we cannot queue task, then we try to add a new* thread.  If it fails, we know we are shut down or saturated* and so reject the task.*/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);}}else if (!addWorker(command, false)) {reject(command);}}

查看 tomcat 线程池的 execute 方法,感觉非常眼熟,这不就是 jdk 线程池的代码吗?其实这里是在 workQueue#offer 做了文章

org.apache.tomcat.util.threads.TaskQueue

java">public class TaskQueue extends LinkedBlockingQueue<Runnable> {
​@Overridepublic boolean offer(Runnable o) {//we can't do any checksif (parent==null) { // 如果parent为空,和JDK线程池没区别return super.offer(o);}//we are maxed out on threads, simply queue the objectif (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) { // 运行中的线程数等于最大线程数,任务入队return super.offer(o);}//we have idle threads, just add it to the queueif (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) { // 未完成线程数 <= 运行中的线程数,任务入队return super.offer(o);}//if we have less threads than maximum force creation of a new threadif (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) { // 运行中的线程数量 < 最大线程数,返回失败,让它去创建线程return false;}//if we reached here, we need to add it to the queuereturn super.offer(o);}
}

总结

本文主要介绍了 Tomcat 线程池的运行流程,和 JDK 线程池的流程比起来,它确实不一样。

而 Tomcat 线程池为什么要这样做呢?其实就是因为 Tomcat 处理的多是 IO 密集型任务,用户在前面等着响应呢,结果你明明还能处理,却让用户的请求入队等待?这样不好。说到底,又回到了任务类型是 IO 密集型还是 CPU 密集型这个话题上来,比起去改 JDK 的线程池参数,Tomcat 线程池又给了我们一个新的思路

参考

每天都在用,但你知道 Tomcat 的线程池有多努力吗?


http://www.ppmy.cn/server/122320.html

相关文章

spark,poi,jar包冲突(commons.io)

1.查看报错类是属于哪个jar包 System.out.println("ZipArchiveInputStream类是&#xff1a;" ZipArchiveInputStream.class.getProtectionDomain().getCodeSource().getLocation()); System.out.println("UnsynchronizedByteArrayOutputStream 类是&#xff1…

BeautifulSoup4在爬虫中的使用

简称bs4&#xff0c;是一个工具箱&#xff0c;通过解析文档为用户提供需要抓取的数据 bs4是Python的一个库&#xff0c;最主要的功能是从网页中获取数据 一、bs4支持的解析器 1、Python标准库 2、lxml HTML解析器 lxml匹配结构规则 3、html5lib 二、提取数据 1、根据标…

力扣 中等 445.两数相加 II

文章目录 题目介绍题解 题目介绍 题解 首先反转两个链表&#xff0c;再调用 2. 两数相加 链接的代码&#xff0c;得到链表&#xff0c;最后将其翻转即可。 class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {l1 reverseList(l1);l2 reverseList(l…

【国家博物馆应对黄牛办法解析】

一 国家博物馆预约流程及独家预约问题 微信公众号的预约引导页&#xff0c;有微信小程序和PC端预约两种方式&#xff1a; PC端预约和微信小程序明明是两中方式&#xff0c;现在却变成一种了&#xff0c; 为何不能在支付宝小程序预约&#xff1f; 独家的背后往往有故事&#x…

村田发布全球最小016008尺寸MLCC电容

全球积层陶瓷电容&#xff08;MLCC&#xff09;领域的领航者——村田制作所&#xff08;Murata Mfg&#xff09;&#xff0c;再次以科技创新的璀璨光芒照亮了电子元器件的微小世界&#xff0c;震撼发布了其全球范围内前所未有的“016008”尺寸MLCC产品。这款产品的问世&#xf…

FOC代码详解介绍(转载)

1、SimpleFOC&#xff08;八&#xff09;—— 理论实践 深度分析SVPWM_svpwm的原理及法则推导和控制算法详解-CSDN博客 2、SVPWM算法原理及详解-CSDN博客 3、FOC和SVPWM的C语言代码实现_svpwm代码-CSDN博客 4、[FOC-Stm32]设置PWM占空比&#xff08;比较值&#xff09;的几种方…

【PG备份恢复】基于时间点的恢复(踩坑指南)

目录 1 设置基于时间点恢复所需的配置 1.1 修改配置文件 postgresql.conf 1.2 生效配置 2 进行一次全备 3 模拟增量数据插入 4 查看当前时间点&#xff0c;LSN号&#xff0c;XID 事务ID&#xff0c;当前的WAL 文件等 5 进行一次WAL 日志的切换 6 模拟故障发生 7 进行…

18-pg内核之日志管理器(六)checkpoint

概念 数据库中除了实际存储的数据之外&#xff0c;还存在许多事务相关的日志&#xff0c;如WAL日志&#xff0c;CLOG日志。MultiXact日志等&#xff0c;每次包含DML操作的事务都会产生这些日志&#xff0c;随着时间的推移&#xff0c;如果不进行清理&#xff0c;日志会一直增大…