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

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

再聊聊拒绝策略


拒绝策略需要看这个方法:

org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable, long, java.util.concurrent.TimeUnit)

看一下该方法上的注释:


如果队列满了,则会等待指定时间后再次放入队列。


如果再次放入队列的时候还是满的,则抛出拒绝异常。


这个逻辑就类似于你去上厕所,发现坑位全都被人占着。这个时候你的身体告诉你,你括弧肌最多还能在忍一分钟。


于是,你掐着表在门口,深呼吸,闭眼冥想,等了一分钟。


运气好的,再去一看:哎,有个空的坑位了,赶紧占着。


运气不好,再去一看:哎,还是没有位置,怎么办呢?抛异常吧。具体怎么抛就不说了,自行想象。


所以我们看看这个地方,Tomcat 的代码是怎么实现的:


catch 部分首先判断队列是不是 Tomcat 的自定义队列。如果是,则进入这个 if 分支。

关键的逻辑就在这个 if 判断里面了。


可以看到 172 行:


if (!queue.force(command, timeout, unit))

调用了队列的 force 方法。我们知道 BlockingQueue 是没有 force 方法的。


所以这个force 是 Tomcat 自定义队列特有的方法:


public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
   if (parent == null || parent.isShutdown())
        throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
   //forces the item onto the queue, to be used if the task is rejected
   return super.offer(o,timeout,unit);
}


进去一看发现:害,也不过如此嘛。就是对原生 offer 方法的一层包装而已。


如果成功加入队列则万事大吉,啥事没有。


如果没有成功加入队列,则抛出异常,并维护 submittedCount 参数。


前面说过:submittedCount 参数 = 队列中的任务个数 + 正在运行的任务数。

所以,这里需要进行减一操作。


拒绝策略就说完了。


但是这个地方的源码是我带你找到的。如果你想自己找到应该怎么操作呢?


你想啊,你是想测试拒绝策略。那只要触发其拒绝策略就行了。


比如下面这样:


给一个只能容纳 450 个任务的线程池提交 500 个任务。

然后就会抛出这个异常:


就会找到 Tomcat 线程池的 174 行:


然后你打上一个断点,玩去吧。


那我们刚刚说的,可以在门口等一分钟再进坑是怎么回事呢?


我们把参数告诉线程池就可以了,比如下面这样:


然后再去运行,因为队列满了后,触发拒绝异常,然后等 3 秒再去提交任务。而我们提交的一个任务 2 秒就能被执行完。


所以,这个场景下,所有的任务都会被正常执行。


现在你知道为了把你给它的任务尽量快速、全部的执行完成,Tomcat有多努力了吗?


小彩蛋


在看 Tomcat 自定义队列的时候我发现了作者这样的注释:


这个地方作用是把 forcedRemainingCapacity 参数设置为 0。


这个参数是在什么时候设置的呢?


就是下面这个关闭方法的时候:

org.apache.tomcat.util.threads.ThreadPoolExecutor#contextStopping


可以看到,调用 setCorePoolSize 方法之前,作者直接把 forcedRemainingCapacity 参数设置为了 0。


注释上面写的原因是JDK ThreadPoolExecutor.setCorePoolSize 方法会去检查 remainingCapacity 是否为 0。


至于为什么会去做这样的检查,Tomcat 的作者两次表示:I don't see why。I did not understand why。

so,他 fake 了 condition。


总之就是他说他也不明白为什么JDK 线程池 setCorePoolSize 方法调小核心线程池的时候要的限制队列剩余长度为 0 ,反正这样写就对了。

别问,问就是规定。


于是我去看了 JDK 线程池的 setCorePoolSize 方法,发现这个限制是在 jdk 1.6 里有,1.6 之后的版本对线程池进行了大规模的重构,取消了这个限制:


那 Tomcat 直接设置为 0 会带来什么问题呢?


正常的逻辑是队列剩余大小 = 队列长度 - 队列里排队的任务数。


而当你对其线程池(队列长度为300)进行监控的时候正常情况应该是这样:


但是当你调用 contextStopping 方法后可能会出现这样的问题:


很明显不符合上面的算法了。


好了,如果你们以后需要对 Tomcat 的线程池进行监控,且 JDK 版本在 1.6版本以上。那你可以去掉这个限制,以免误报警。


好了,恭喜你,朋友。又学到了一个基本用不上的知识点,奇怪的知识又增加了一点点。


目录
相关文章
|
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 的线程池有多努力吗? (2)
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (2)
216 0
|
Java 应用服务中间件 程序员
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (1)
每天都在用,但你知道 Tomcat 的线程池有多努力吗? (1)
116 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