java并发之ThreadPoolExecutor分析
ThreadPoolExecutor线程池是我们平时使用最多的线程池处理工具,充分理解线程池的实现原理,可以让我们在碰到相关问题时有效的进行参数调优与处理,进而提高程序的整体性能与吞吐量。本文依托于jdk1.8,但不会讨论代码的实现细节,需要注意的是ctl属性,ctl是把两个属性合并到一个int字段中进行记录,高3位记录线程池状态,低29位记录线程池数量。这样的做法仁者见仁智者见智,复杂性增加不说,性能到底能提升多少呢?大家在阅读的时候可以把int理解为长度为32的数组。
“一个中心,两个基本点”,我们在学生时代背诵了无数次的政治学内容,也同样适用于ThreadPoolExecutor。他的中心是以BlockingQueue为数据中心,以corePoolSize,与maximumPoolSize为两个基本点,只要我们充分理解了他们的作用,就抓住了了解ThreadPoolExecutor的钥匙。以下我将用图片的方式描述这几个字段的实际意义
简单解释一下上图的各个部分,整个大框代表了ThreadPoolExecutor类,我们可以把orePoolSize,与maximumPoolSize理解为两个阀值,在不同的阶段线程池表现的行为是有所不同的,poolSize是当前运行的线程池数量(ThreadPoolExecutor内部并不存在这个字段,这个字段存在于jdk1.6中,现在存在ctl后29位之中),works存放当前的工作线程,blockQueue存放我们的任务单元。假设现在线程池已初始化好了,并且内部状态如上图在等待我们的输入。这时我们执行了execute方法。那么
1,如果poolSize<corePoolSize,线程池会直接创建新线程进行任务执行,如图
将poolsize指针上移,将新线程存储在works中,以便后来任务使用。
2如果poolSize>=corePoolSize,这时有两种情况,A如果blockqueue没有填满,直接将数据填入如队列中,如图
B,如果队列满了,则创建新线程进行任务任务执行,如图
这里需要解释一下,第二步实际上是该线程池最核心最精华的部分。在通常情况下,如果任务平稳的提交到线程池中,并且各个参数都设计的合理的话,线程池一直是以corePoolSize数量的线程在运行。所以corePoolSize值的设置大小很重要,同时要参考blockqueue的大小一起配置。corePoolSize太大会造成不必要的浪费,太小会造成线程池不断波动。 Blockqueue的大小也很重要,不建议不指定其大小,这样会使用int的最大值做初始化,当系统忙碌的时候会产生大量的任务积压,而且容易内存溢出,也把线程池的最后救命稻草拔掉了。上述是平稳的情况,如果有峰值情况怎么办,没关系,这时候maximumPoolSize开始起作用了,如上图,这里的关键是什么,就是新创建的线程不会立刻销毁,它会加入之前的线程池中,与大家一起消费blockqueue中的任务,系统中最坏的情况下可以创建maximumPoolSize个线程,大家同心协力一起挺过这个繁忙的时刻。这与我们在日常生活的情形一样,如果一下要完成太多的任务,项目组会马上会通过招人,从别的组调用等等措施,增加人手,提高整体战斗力。
3,如果poolSize>=maximumPoolSize,线程池已经达到最大负载了,这个时候怎么办,还有办法。如图
说实话,这时候线程池已经无能为力了,他搞不定了,他不搞的时候问题就抛给你了,这个时候你需要选择一种策略来应对这种情况,当然最常用的抛出异常,线程池到了这个程度已经基本快崩溃了。RejectedExecutionHandler是最后应急措施,当然这种情况要我们自己处理。
4,如果我们在3没有挺过来,估计已经宕机了,我们假设大家同心协力已经共度难关了。现在的问题是这么多线程该如何回收呢?管杀不管埋显然不是我们的风格。当blockqueue队列空并且poolSize>corePoolSize的时候,线程开始回收,直到只剩下corePoolSize个线程的时候为止。一切又恢复了最初,就好像什么都没发生过一样,英雄从来都是这样,逢乱世立马横尸,战乱结束,归田卸甲。
至此,整个ThreadPoolExecutor讨论完了,核心思路就是不赶工的时候几个人慢慢干,一旦突然来大任务了,多找几个人干,干完了该滚蛋滚蛋。这时候可能有同学会问了,说了这么多,有没有我们自己表现的机会呢,也就是他有什么扩展点么?有的有的。比如ScheduledThreadPoolExecutor类,他的核心思路只是把blockqueue队列换成了根据时间先后排序的优先级队列,就完成了定时器调度工作。同时,我们也可以在我们的任务执行前后进行一些自定义处理,继承时只要实现beforeExecute,afterExecute接口即可。