每天都在用,但你知道 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版本以上。那你可以去掉这个限制,以免误报警。


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


目录
相关文章
|
3月前
|
Java 应用服务中间件
面对海量网络请求,Tomcat线程池如何进行扩展?
【10月更文挑战第4天】本文详细探讨了Tomcat线程池相较于标准Java实用工具包(JUC)线程池的关键改进。首先,Tomcat线程池在启动时即预先创建全部核心线程,以应对启动初期的高并发请求。其次,通过重写阻塞队列的入队逻辑,Tomcat能够在任务数超过当前线程数但未达最大线程数时,及时创建非核心线程,而非等到队列满才行动。此外,Tomcat还引入了在拒绝策略触发后重新尝试入队的机制,以提高吞吐量。这些优化使得Tomcat线程池更适应IO密集型任务,有效提升了性能。
面对海量网络请求,Tomcat线程池如何进行扩展?
|
3月前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
181 0
剖析Tomcat线程池与JDK线程池的区别和联系!
|
8月前
|
Java 应用服务中间件
Springboot启动的时候初始化的线程池默认配置tomcat
Springboot启动的时候初始化的线程池默认配置tomcat
200 1
|
8月前
|
域名解析 安全 Java
SpringBoot启动的时候初始化的线程池默认配置tomcat
SpringBoot启动的时候初始化的线程池默认配置tomcat
81 1
|
Java 应用服务中间件 调度
Tomcat 线程池
Tomcat 线程池
|
Java 应用服务中间件
98分布式电商项目 - Tomcat性能优化(使用线程池)
98分布式电商项目 - Tomcat性能优化(使用线程池)
72 0
|
网络协议 Java 应用服务中间件
详解Tomcat的连接数与线程池,调优必备
详解Tomcat的连接数与线程池,调优必备
|
存储 Java 应用服务中间件
|
前端开发 JavaScript Java
Tomcat使用线程池配置高并发连接
Tomcat使用线程池配置高并发连接1:配置executor属性 打开/conf/server.xml文件,在Connector之前配置一个线程池: namePrefix="tomcatThreadPool-" maxThreads="1000" maxIdleTime="300000" minSpareThreads="200"/> 重要参数说明:name:共享线程池的名字。
2016 0
|
Java 应用服务中间件
Tomcat线程池配置
1:配置executor属性 打开/conf/server.xml文件,在Connector之前配置一个线程池: 重要参数说明: name:共享线程池的名字。这是Connector为了共享线程池要引用的名字,该名字必须唯一。
1295 0