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

简介: 每天都在用,但你知道 Tomcat 的线程池有多努力吗? (1)

荒腔走板


大家好,我是 why,一个四川程序猿,成都好男人


先是本号的特色,技术分享之前先简短的荒腔走板聊聊生活。让文章的温度更多一点点。

上面的图是我在一次跑步的过程中拍的。活动之前赛事方搞了个留言活动,收集每公里路牌的一个宣传语。


我的留言有幸被选中了:


每人知道你在坚持什么,但你自己心里应该清楚。


是在说跑马拉松,也是在说其他的事情。


我记得那天的太阳,骄阳似火,路上的树荫也非常的少。苦就苦在我还报的是超级马拉松(说是超级马拉松,其实就是一个全马 42 km加最后 3 km纯上坡的马拉松)


到底有多晒,我给你看一下对比:


酷暑难耐,以至于 30 公里左右的地方我的心里出现了两个小人:


一个说:我好累啊,我跑不动了,我要退赛。


一个说:好呀好呀,我也好晒啊,退赛退赛。


我说:呸,看你们两个不争气的东西,让我带你去终点


于是在 36 公里的地方碰到了我提交的标语,非常开心,停下来拍了几张照片。给自己说:坚持不住的时候再坚持一下。


最后的 3 公里上坡,抽筋了不知道多少次。远远看见终点拱门的时候我突然想到了在敦煌的时候悟出的一句话:自己给自己的辛苦,不是辛苦,是幸福。

好了,说回文章。


违背直觉的JDK线程池


先用 JDK 线程池来开个题。


还是用我之前这个文章《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。》“先劝退一波”这一小节里面的例题:


问:这是一个自定义线程池,假设这个时候来了 100 个比较耗时的任务,请问有多少个线程在运行?


正确回答在之前的文章中回答了,这里不在赘述。


但是我面试的时候曾经遇到过很多对于 JDK 线程池不了解的朋友。


而这些人当中大多数都有一个通病,那就是遇到不太会的问题,那就去猜。


面试者遇到这个不会的题的时候,表面上微微一笑,实际上我都已经揣摩出他们的内心活动了:


MD,这题我没背过呀,但是刚刚听面试官说核心线程数是 10,最大线程数是 30。看题也知道答案不是 10 就是 30。


选择题,百分之 50 的命中率,难道不赌一把?


等等,30 是最大线程数?最大?我感觉就是它了。


于是在电光火石一瞬间的思考后,和我对视起来,自信的说:


于是我也是微微一笑,告诉他:下去再了解一下吧,我们聊聊别的。


确实,如果完全不了解 JDK 线程池运行规则,按照直觉来说,我也会觉得应该是,不管是核心还是最大线程数,有任务来了应该先把线程池里面可用的线程用完了,然后再把任务提交到队列里面去排队。


可惜 JDK 的线程池,就是反直觉的。


那有符合我们直觉的线程池吗?


有的,你经常用的的 Tomcat ,它里面的线程池的运行过程就是先把最大线程数用完,然后再提交任务到队列里面去的。

我带你剖析一下。


Tomcat线程池


先打开 Tomcat 的 server.xml 看一下:


眼熟吧?哪一个学过 java web 的人没有配置过这个文件?哪一个配置过这个文件的人没有留意过 Executor 配置?


具体的可配置项可以查看官方文档:


http://tomcat.apache.org/tomcat-9.0-doc/config/executor.html

同时我找到一个可配置的参数的中文说明如下:


注意其中的第一个参数是 className,图片中少了个字母 c。


然后还有两个参数没有介绍,我补充一下:


1.prestartminSpareThreads:boolean 类型,当服务器启动时,是否要创建出最小空闲线程(核心线程)数量的线程,默认值为 false 。


2.threadRenewalDelay:long 类型,当我们配置了

ThreadLocalLeakPreventionListener 的时候,它会监听一个请求是否停止。当线程停止后,如果有需要,会进行重建,为了避免多个线程,该设置可以检测是否有 2 个线程同时被创建,如果是,则会按照该参数,延迟指定时间创建。 如果拒绝,则线程不会被重建。默认为 1000 ms,设定为负值表示不更新。


我们主要关注 className 参数,如果不配置,默认实现是:


org.apache.catalina.core.StandardThreadExecutor

我们先解读一下这个方法(注意,本文中 Tomcat 源码版本号为:10.0.0-M4):

org.apache.catalina.core.StandardThreadExecutor#startInternal


从 123 行到 130 行,就是构建 Tomcat 线程池的地方,很关键,我解读一下:


123行


taskqueue = new TaskQueue(maxQueueSize);

创建一个 TaskQueue 队列,这个队列是继承自 LinkedBlockingQueue 的:


该队列上的注释值得关注一下:


主要是说这是一个专门为线程池设计的一个任务队列。配合线程池使用的时候和普通队列有不一样的地方。


同时传递了一个队列长度,默认为 Integer.MAX_VALUE:


124行


TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());

构建一个 ThreadFactory,其三个入参分为如下:


namePrefix:名称前缀。可以指定,其默认是“tomcat-exec-”。


daemon:是否以守护线程模式启动。默认是 true。


priority:线程优先级。是一个 1 到 10 之前的数,默认是 5。


125行


executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);

构建线程池,其 6 个入参分别如下:


这个具体含义我就不解释了,和 JDK 线程池是一样的。

只是给大家看一下默认参数。


另外还需要十分注意的一点是,这里的 ThreadPoolExecuteor 是 Tomcat 的,不是 JDK 的,虽然名字一样。


看一下 Tomcat 的 ThreadPoolExecuteor注释,里面提到了两个点,一是已提交总数,二是拒绝策略。后面都会讲到。


126行


executor.setThreadRenewalDelay(threadRenewalDelay);

设置 threadRenewalDelay 参数。不是本文重点,可以先不关心。


127 - 129行


if (prestartminSpareThreads) {
    executor.prestartAllCoreThreads();
}


设置是否预启动所有的核心线程池,这个参数在之前文章中也有讲到过。


prestartminSpareThreads 参数默认是 false。但是我觉得这个地方你设置为 true 也是多次一举。完全没有必要。

为什么呢?


因为在 125 行构建线程池的时候已经调用过这个方法了:


从源码可以看出,不管你调用哪一个线程池构造方法,都会去调用 prestartAllCoreThreads 方法。


所以,这算不算 Tomcat 的一个小 Bug 呢?快拿起你的键盘给它提 pr 吧。

目录
相关文章
|
5天前
|
Java 应用服务中间件
Springboot启动的时候初始化的线程池默认配置tomcat
Springboot启动的时候初始化的线程池默认配置tomcat
22 1
|
10月前
|
Java 应用服务中间件 调度
Tomcat 线程池
Tomcat 线程池
|
7月前
|
Java 应用服务中间件
98分布式电商项目 - Tomcat性能优化(使用线程池)
98分布式电商项目 - Tomcat性能优化(使用线程池)
32 0
|
9月前
|
网络协议 Java 应用服务中间件
详解Tomcat的连接数与线程池,调优必备
详解Tomcat的连接数与线程池,调优必备
|
Java 应用服务中间件 Apache
Tomcat 线程池学习总结
Tomcat 线程池学习总结
419 0
|
Dubbo Java 应用服务中间件
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (4)
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (4)
100 0
|
监控 算法 Java
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (3)
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (3)
100 0
|
Java 应用服务中间件
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (2)
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (2)
216 0
|
监控 Java 应用服务中间件
美团动态线程池思路开源框架(DynamicTp),动态调整Tomcat、Jetty、Undertow线程池参数篇
大家好,这篇文章我们来介绍下动态线程池框架(DynamicTp)的adapter模块,上篇文章也大概介绍过了,该模块主要是用来适配一些第三方组件的线程池管理,让第三方组件内置的线程池也能享受到动态参数调整,监控告警这些增强功能。
723 1
美团动态线程池思路开源框架(DynamicTp),动态调整Tomcat、Jetty、Undertow线程池参数篇
|
前端开发 JavaScript Java
Tomcat使用线程池配置高并发连接
Tomcat使用线程池配置高并发连接1:配置executor属性 打开/conf/server.xml文件,在Connector之前配置一个线程池: namePrefix="tomcatThreadPool-" maxThreads="1000" maxIdleTime="300000" minSpareThreads="200"/> 重要参数说明:name:共享线程池的名字。
1862 0