简介
如题,本文将重点解析 ThreadPoolExecutor 用到的数据结构背后存在的思考,如果还有朋友还不是很清楚 ThreadPoolExecutor 是一个什么事物,建议阅读一下 入门。
Task 存放的数据结构为 BlockingQueue<Runnable>,这个打开源码,或者查看注释文档都能看的出来。这里引用一下对于ThreadPoolExecutor里 workQueue 解释的原始文档:
Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:
1、If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
2、If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
3、If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
上述首先提到 BlockingQueue 用于存放提交的 task,后面的三点讲述的是 线程创建的条件,以及 task 是被如何存放,拒绝处理的。如果要理解以上文档的含义,还需要先了解两个概念,在创建 ThreadPoolExecutor 会经常接触到。
corePoolSize:最小活跃的线程数,受 keepAliveTime、allowCoreThreadTimeOut 等影响,线程池创建即使设置为非0,在没有调用 prestartCoreThread / prestartAllCoreThreads,活跃的线程数是0,timeOut也能影响活跃线程数量。
maximumPoolSize:线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。
在理解了上面的两个概念,对照文档中的三项原则,你应该能理解线程池是 何时 创建线程,也阐述了 task是存放到 queue, 甚至被 拒绝处理。
BlockingQueue 是一个接口定义,那么不同 BlockingQueue 实现结合 corePoolSize、maximumPoolSize 是否能影响线程池的运行方式?
Doug Lea 给出的常用三种策略队列模式:
1、Direct handoffs. 这个模式的名字我不打算翻译,因为我觉得翻译得不好。Doug Lea给出的 Direct handoffs 队列为 SynchronousQueue,SynchronousQueue 的 Direct handoffs 特性使得一定会有线程(已存在或者新创建的)来处理刚刚提交的 Task,否则提交失败。如果 Task 的处理速度小于提交速度,那么线程数会一直增加 。官方给的策略是将 maximumPoolSizes 设置为 Integer.MAX_VALUE,这样能尽量避免 Task 提交失败,但处理 Task 的速度小于 Task 的提交速度则会导致线程数太多,使用参考 Executors.newCachedThreadPool() ,但实际开发中,可以根据需求设置 maximumPoolSizes 值防止可能出现线程创建过多的情况. 在将 maximumPoolSizes 设置得比较充盈的情况下,个人觉得比较适合处理一些需要立即得到执行的 Task 这类业务。
2、Unbounded queues. 无限长度的队列模式,如 LinkedBlockingQueue ,这种模式的队列,进入队列是不会失败的(内存充足的情况下),那么只需要根据自己的需求设置 corePoolSize 即可 (注意,此类模式下 maximumPoolSizes 已经失效,参考 原则二),如果 Task 的消费速度太慢,则会遇到太多任务处于等待中的情况;使用参考 Executors.newFixedThreadPool(int nThreads). 此模式在控制线程数的前提下,保证所有提交上来的 Task 都能被执行,但不保证立刻被执行,当 Task 的处理速度小于 Task 的提交速度,可能出现内存不足,这一点很关键,本人线上就遇到这样的情况,典型的消费速度小于生产速度的问题。
3、Bounded queues. 有限长度的队列模式,如 ArrayBlockingQueue,这种模式个人感觉是一个定制化的选择,通过指定 maximumPoolSizes,队列(长度),来控制线程池对系统的影响。比如是大队列小池,这样会降低 CPU 的使用率和系统资源占用、上下文切换,但同时也降低了处理速度;如果是小队列大池,这样能得到较高 CPU 使用率,但上下文切换的开销也会遇到瓶颈,也会降低处理速度。个人感觉这种模式是以上两种模式的补充,具体使用还需要根据自身的业务场景进行设置,这也可能是官方目前没有给出使用参考的缘由。
对于其他特性的队列,如:PriorityBlockingQueue 我觉得也是可行的。对于第三点模式下的系统影响阐述,在任何模式下同样有意义。在使用对应模式下的线程池,务必了解其中的优缺点。
到这里,我觉得 最初的问题 已经有了答案。但还有一些疑惑,扩展性如此好的设计,肯定还有其他不为人知考虑,敬请期待后续!感谢阅读,如有理解上的出入,欢迎拍砖吐槽,不要怜惜我!
网友评论