兜兜零元 发表于 2024-5-19 00:07:50

Java线程池核心线程用尽后为何优先排队而不是继续创建线程直至最大线程数?

前阵子在v2ex上看到这篇帖子讨论这个标题,有意思的是这个如此基础的标题在Javaer的天下里并没有广泛的共识,下面的答复也是七嘴八舌的,刚幸亏《Java Performace》上看到对这个标题的解释,实验总结一下。
原因

书中对线程池的解释基于以下几点前提:

[*]如果CPU已经跑满,增加线程并不能提高体系吞吐,更多的线程切换开销反而会降低性能
[*]核心线程用尽之后CPU负载怎样线程池并不清楚,这取决于核心线程数的巨细以及当前任务的性子(CPU密集还是IO密集)
[*]线程池不一定要用满所有CPU,偶然线程数本来就是一种CPU资源限制的手段
理想情况下线程池中Runnable的线程数应该刚好等于CPU核心数,如果任务都是CPU密集型,那么线程数就等于核心数;如果是IO密集型,那么就需要计算CPU耗时和IO耗时的比例来调解核心线程数。实际中的情况往往更加复杂,任务可能有CPU密集的也有IO密集的,IO密集的耗时比例也不尽雷同。调解核心线程数、最大线程数和队列长度来获得理想的体系吞吐和请求耗时这是开发者的责任,线程池提供机制但无法办理这个标题。
线程池的逻辑或者说约定:

[*]假设需要核心线程数的线程来使CPU达到饱和。如果当前线程数没有达到核心线程数,线程池总是新建线程来执行任务,即使现有线程有空闲的。
[*]如果现有线程数达到核心线程数而队列未满,将任务推进队列。此时假设CPU资源已经饱和,任务需要等待CPU资源开释,再增加线程只会降低性能。
[*]如果连队列都已经满了,继续创建线程直至最大线程数。此时新提交的任务依然排队,从队头取一个任务交给新创建的线程。此时线程池以为体系已经过载,创建新线程属于试试能不能抢救一下。
[*]最大线程数都达到了,再有新任务提交直接调用拒绝策略。
如上,可见设置的关键是核心线程数,核心线程数应该尽量使CPU饱和(或者达到我们期望的负载)但又不会产生过多的上下文切换。思量到任务的复杂性,这个参数确实只能通过压力测试来得到。
其它的一些模式

ThreadPoolExecutor的配置是非常灵活的,可能通过调解参数使得线程池接纳一些别的行为。
令核心线程数等于最大线程数,就可以取得原贴所期望的,可能也是大部门人所期望的,到达最大线程数后再排队。不过核心线程是不会被回收的,如果确实需要回收可以设置allowCoreThreadTimeOut。如果使用无容量限制的队列如 LinkedBlockedingQueue那么行为就和Executors#newFixedThreadPool雷同。
令队列长度等于0,最大线程数等于无限(Integer#MAX_VALUE,等效于无限),此时所有任务都会直接提交给线程,没有空闲的就新建,不会有任务排队。此时等效于Executors#newCachedThreadPool。
上面两种方式多多少少都有点标题,这也是为什么不建议通过Executors来创建线程池。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Java线程池核心线程用尽后为何优先排队而不是继续创建线程直至最大线程数?