背景:JDK的线程池的运作原理 :
JDK的线程池的构造函数有7个参数,分别是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。先复习一下 https://blog.csdn.net/ye17186/article/details/89467919
上述内容是网上对于线程池的参数的描述,需要关注的一个点:
1)任务进入线程池队列,先启动WORKER线程到 CORE的数量,然后再填写线程池的缓冲队列,队列满了以后(如果可以满),再增加WORKER线程到MAX配置数量。
下面是源码:(源码就是这样,也验证过,是这个逻辑,简单说,先CORE 再队列,队列满了以后再拉线程到MAX数量)
2)这样会有缺陷,为什么一定要 满足 队列满了,再拉线程,我想优先拉线程怎么办 ? 不好意思,JDK只有一个办法:设置队列大小为1,那么就基本上马上就会拉新线程了(但是作为数据缓冲区的队列,削峰填谷的优势就无法使用了)
那么如果期望 优先 先拉线程数量到MAX,再把任务放到队列缓存区怎么办 ?
业务场景需要满足: 1)任务量大了,线程立即到拉到MAX 数量跑业务。 2)线程到MAX了,额外的队列可以存储任务,做削峰填谷使用。
(core size是闲时最低线程数量,如果线程数量太多,会导致线程内存开销以及 太多线程无效轮询消耗)
方案一: 看到源码,自己去实现一个线程池的实现,重写EXECUTE方法,调整逻辑,先拉WORKER到MAX然后放入队列可以达成
项目中,准备上方案一的时候,发现代码量不小,这个问题应该很多人都遇到了,有没有现成的可以使用?
重写JDK源码会带来工作量,维护,以及升级JDK的风险,明显是一种不好的办法。
方案二:再找就发现了这样一篇文档:
https://blog.csdn.net/qq271859852/article/details/104860255/
果然,遇到这个问题的不止一个, 事实上 tomcat 和dubbo 都这样干了!
非常有意思的是,是在队列的OFFER函数中做文章(有点欺骗JDK的线程池 EXECUTE的意思)很难想到这种方案。
我们要使用:两行代码就满足了我们的诉求:
引用对象:当然是用DUBBO也是一样,已验证、有效。
PS: 额外的思考:
1)为什么?JDK默认要使用先队列再拉MAX WORKER的方式? 可能是JDK认为线程池任务大多数都是 CPU密集型的任务,那么启动太多的CPU带来的价值不大、或者拉线程的有成本,队列满了再拉。
2)怎么选?如果任务是CPU密集使用JDK自带的线程池策略就够了,如果是IO型任务,有较多阻塞场景(这就是为什么TOMCAT 和RPC的DUBBO需要的原因)就需要使用线程数优先,再进入队列的模式(IO密集型任务,有IO阻塞,JDK自带线程池的策略难以满足要求)。
3)客观的说,JDK应该把这个配置 做成配置项(到底是先进入队列,再拉线程到MAX,还是先拉线程到MAX再进入队列)