填个坑!再谈线程池动态调整那点事。(中)

简介: 填个坑!再谈线程池动态调整那点事。(中)

除了改 setCapacity 方法之外,我在写文章的时候不经意间还触发了另外一个答案:

image.png

在调用完 setCapacity 方法之后,再次调用 put 方法,也能得到预期的输出:

image.png

我们观察 put 方法就能发现其实道理是一样的:

image.png

当调用完 setCapacity 方法之后,再次调用 put 方法,由于不满足标号为 ① 的代码的条件,所以就不会被阻塞。

于是可以顺利走到标号为 ② 的地方唤醒被阻塞的线程。

所以也就变相的达到了改变队列长度,唤醒被阻塞的任务目的。

而究根结底,就是需要执行一次唤醒的操作。

那么那一种优雅一点呢?

那肯定是第一种把逻辑封装在 setCapacity 方法里面操作起来更加优雅。

第二种方式,大多适用于那种“你也不知道为什么,反正这样写程序就是正常了”的情况。

image.png

现在我们知道在线程池里面动态调整队列长度的坑是什么了。

那就是队列满了之后,调用 put 方法的线程就会被阻塞住,即使此时另外的线程调用了 setCapacity 方法,改变了队列长度,如果没有线程再次触发 put 操作,被阻塞的线程也不会被唤醒。

是不是?

了不了解?

对不对?

这是不对的,朋友们。

image.png

看到前面内容,频频点头的朋友,要注意了。

这地方要开始转弯了。


开始转弯


线程池里面往队列里面添加对象的时候,用的是 offer 命令,并没有用 put 命令:

image.png

我们看看 offer 命令在干啥事儿:

image.png

队列满了之后,直接返回 false,不会出现阻塞的情况。

也就是说,线程池中根本就不会出现我前面说的需要唤醒的情况,因为根本就没有阻塞中的线程。

在和大佬交流的过程中,他提到了一个 VariableLinkedBlockingQueue 的东西。

这个类位于 MQ 包里面,我前面提到的 setCapacity 方法的修改方式就是在它这里学来的:


image.png

同时,项目里面也用到了它的 put 方法:

image.png

所以,它是有可能出现我们前面分析的情况,有需要被唤醒的线程。

但是,你想想,线程池里面并没有使用 put 方法,是不是就刚好避免这样的情况?

是的,确实是。

但是,不够严谨,如果知道有问题了的话,为什么要留个坑在这里呢?

你学 MQ 的 VariableLinkedBlockingQueue 考虑的周全一点,就算 put 方法阻塞的时候也能用,它不香吗?

写到这里其实好像除了让你熟悉一下 LinkedBlockingQueue 外,似乎是一个没啥卵用的知识点,

但是,我能让这个没有卵用的知识点起到大作用。

因为这其实是一个小细节。

假设我出去面试,在面试的时候提到动态调整方法的时候,在不经意间拿捏一下这个小细节,即使我没有真的落地过动态调整,但是我提到这样的一个小细节,就显得很真实。

面试官一听:很不错,有整体,有局部,应该是假不了。

image.png

在 VariableLinkedBlockingQueue 里面还有几处细节,拿 put 方法来说:

image.png

判断条件从 count.get() >= capacity 变成了 count.get() = capacity,目的是为了支持 capacity 由大变小的场景。

这样的地方还有好几处,就不一一列举了。

魔鬼,都在细节里面。

同学们得好好的拿捏一下。


JDK bug


其实原计划写到前面,就打算收尾了,因为我本来就只是想补充一下我之前没有注意到的细节。

但是,我手贱,跑到 JDK bug 列表里面去搜索了一下 LinkedBlockingQueue,想看看还有没有什么其他的收获。

我是万万没想到,确实是有一点意外收获的。

首先是这一个 bug ,它是在 2019-12-29 被提出来的:

https://bugs.openjdk.java.net/browse/JDK-8236580


image.png

看标题的意思也是想要给 LinkedBlockingQueue 赋能,可以让它的容量进行修改。

加上他下面的场景描述,应该也想要和线程池配合,找到队列的抓手,下钻到底层逻辑,联动监控系统,拉通配置页面,打出一套动态适应的组合拳。

image.png

但是官方并没有采纳这个建议。

image.png

回复里面说写 concurrent 包的这些哥们对于在并发类里面加东西是非常谨慎的。他们觉得给 ThreadPoolExecutor 提供可动态修改的特性会带来或者已经带来众多的 bug 了。

我理解就是简单一句话:建议还是不错的,但是我不敢动。并发这块,牵一发动全身,不知道会出些什么幺蛾子。

所以要实现这个功能,还是得自己想办法。

这里也就解释了为什么用 final 去修饰了队列的容量,毕竟把功能缩减一下,出现 bug 的几率也少了很多。

image.png

第二个 bug 就有意思了,和我们动态调整线程池的需求非常匹配:

https://bugs.openjdk.java.net/browse/JDK-8241094

image.png


这是一个 2020 年 3 月份提出的 bug,描述的是说在更新线程池的核心线程数的时候,会抛出一个拒绝异常。

在 bug 描述的那部分他贴了很多代码,但是他给的代码写的很复杂,不太好理解。

好在 Martin 大佬写了一个简化版,一目了然,就好理解的多:

微信图片_20220428210147.png


这段代码是干了个啥事儿呢,简单给大家汇报一下。

首先 main 方法里面有个循环,循环里面是调用了 test 方法,当 test 方法抛出异常的时候循环结束。

然后 test 方法里面是每次都搞一个新的线程池,接着往线程池里面提交队列长度加最大线程数个任务,最后关闭这个线程池。

同时还有另外一个线程把线程池的核心线程数从 1 修改为 5。

你可以打开前面提到的 bug 链接,把这段代码贴出来跑一下,非常的匪夷所思。

Martin 大佬他也认为这是一个 BUG.

说实在的,我跑了一下案例,我觉得这应该算是一个 bug,但是经过 Doug Lea 老爷子的亲自认证,他并不觉得这是一个 Bug。

主要是这个 bug 确实也有点超出我的认知,而且在链接中并没有明确的说具体原因是什么,导致我定位的时间非常的长,甚至一度想要放弃。

但是最终定位到问题之后也是长叹一口:害,就这?没啥意思。

先看一下问题的表现是怎么样的:

image.png


目录
相关文章
|
2月前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
209 64
|
3月前
|
监控 Java
G1垃圾回收器的哪些配置参数对性能影响最大,如何调整这些参数
G1垃圾回收器的哪些配置参数对性能影响最大,如何调整这些参数
239 0
|
8月前
|
安全 Java 调度
【C/C++ 线程池设计思路 】设计与实现支持优先级任务的C++线程池 简要介绍
【C/C++ 线程池设计思路 】设计与实现支持优先级任务的C++线程池 简要介绍
241 2
|
8月前
|
Java 关系型数据库 MySQL
线程池高级理论总结
线程池高级理论总结
58 0
|
8月前
|
监控 Java API
【C/C++ 线程池设计思路】如何在C++跨平台应用中精准调节线程池:一个动态适应策略的实践指南
【C/C++ 线程池设计思路】如何在C++跨平台应用中精准调节线程池:一个动态适应策略的实践指南
341 0
|
监控 Java 调度
设置动态线程池参数原理与实践
设置动态线程池参数原理与实践
205 0
设置动态线程池参数原理与实践
|
消息中间件 缓存 资源调度
动态调整线程池参数实践
动态调整线程池参数实践
1236 0
|
缓存 NoSQL Java
线程池:第三章:线程池的手写改造和拒绝策略以及线程池配置合理线程数
线程池:第三章:线程池的手写改造和拒绝策略以及线程池配置合理线程数
199 0
线程池:第三章:线程池的手写改造和拒绝策略以及线程池配置合理线程数
|
缓存 Java 调度
1线程池的七大参数以及他们之间是怎么工作的?
1线程池的七大参数以及他们之间是怎么工作的?
|
监控 Java
填个坑!再谈线程池动态调整那点事。(上)
填个坑!再谈线程池动态调整那点事。(上)
382 0
填个坑!再谈线程池动态调整那点事。(上)

热门文章

最新文章

相关实验场景

更多