欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java并发之ThreadPoolExecutor分析

程序员文章站 2022-04-19 13:51:59
...

java并发之ThreadPoolExecutor分析

         ThreadPoolExecutor线程池是我们平时使用最多的线程池处理工具,充分理解线程池的实现原理,可以让我们在碰到相关问题时有效的进行参数调优与处理,进而提高程序的整体性能与吞吐量。本文依托于jdk1.8,但不会讨论代码的实现细节,需要注意的是ctl属性,ctl是把两个属性合并到一个int字段中进行记录,高3位记录线程池状态,低29位记录线程池数量。这样的做法仁者见仁智者见智,复杂性增加不说,性能到底能提升多少呢?大家在阅读的时候可以把int理解为长度为32的数组。

         “一个中心,两个基本点,我们在学生时代背诵了无数次的政治学内容,也同样适用于ThreadPoolExecutor。他的中心是以BlockingQueue为数据中心,以corePoolSize,与maximumPoolSize为两个基本点,只要我们充分理解了他们的作用,就抓住了了解ThreadPoolExecutor的钥匙。以下我将用图片的方式描述这几个字段的实际意义


java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
 

 

简单解释一下上图的各个部分,整个大框代表了ThreadPoolExecutor类,我们可以把orePoolSize,与maximumPoolSize理解为两个阀值,在不同的阶段线程池表现的行为是有所不同的,poolSize是当前运行的线程池数量(ThreadPoolExecutor内部并不存在这个字段,这个字段存在于jdk1.6中,现在存在ctl29位之中),works存放当前的工作线程blockQueue存放我们的任务单元。假设现在线程池已初始化好了,并且内部状态如上图在等待我们的输入。这时我们执行了execute方法。那么

1,如果poolSize<corePoolSize,线程池会直接创建新线程进行任务执行,如图


java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
 

 

poolsize指针上移,将新线程存储在works中,以便后来任务使用。

2如果poolSize>=corePoolSize,这时有两种情况,A如果blockqueue没有填满,直接将数据填入如队列中,如图


java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
 

 

B,如果队列满了,则创建新线程进行任务任务执行,如图


java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
 

 

这里需要解释一下,第二步实际上是该线程池最核心最精华的部分。在通常情况下,如果任务平稳的提交到线程池中,并且各个参数都设计的合理的话,线程池一直是以corePoolSize数量的线程在运行。所以corePoolSize值的设置大小很重要,同时要参考blockqueue的大小一起配置。corePoolSize太大会造成不必要的浪费,太小会造成线程池不断波动。 Blockqueue的大小也很重要,不建议不指定其大小,这样会使用int的最大值做初始化,当系统忙碌的时候会产生大量的任务积压,而且容易内存溢出,也把线程池的最后救命稻草拔掉了。上述是平稳的情况,如果有峰值情况怎么办,没关系,这时候maximumPoolSize开始起作用了,如上图,这里的关键是什么,就是新创建的线程不会立刻销毁,它会加入之前的线程池中,与大家一起消费blockqueue中的任务,系统中最坏的情况下可以创建maximumPoolSize个线程,大家同心协力一起挺过这个繁忙的时刻。这与我们在日常生活的情形一样,如果一下要完成太多的任务,项目组会马上会通过招人,从别的组调用等等措施,增加人手,提高整体战斗力。

3,如果poolSize>=maximumPoolSize,线程池已经达到最大负载了,这个时候怎么办,还有办法。如图


java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
 

 

说实话,这时候线程池已经无能为力了,他搞不定了,他不搞的时候问题就抛给你了,这个时候你需要选择一种策略来应对这种情况,当然最常用的抛出异常,线程池到了这个程度已经基本快崩溃了。RejectedExecutionHandler是最后应急措施,当然这种情况要我们自己处理。

4,如果我们在3没有挺过来,估计已经宕机了,我们假设大家同心协力已经共度难关了。现在的问题是这么多线程该如何回收呢?管杀不管埋显然不是我们的风格。当blockqueue队列空并且poolSize>corePoolSize的时候,线程开始回收,直到只剩下corePoolSize个线程的时候为止。一切又恢复了最初,就好像什么都没发生过一样,英雄从来都是这样,逢乱世立马横尸,战乱结束,归田卸甲。

         至此,整个ThreadPoolExecutor讨论完了,核心思路就是不赶工的时候几个人慢慢干,一旦突然来大任务了,多找几个人干,干完了该滚蛋滚蛋。这时候可能有同学会问了,说了这么多,有没有我们自己表现的机会呢,也就是他有什么扩展点么?有的有的。比如ScheduledThreadPoolExecutor类,他的核心思路只是把blockqueue队列换成了根据时间先后排序的优先级队列,就完成了定时器调度工作。同时,我们也可以在我们的任务执行前后进行一些自定义处理,继承时只要实现beforeExecuteafterExecute接口即可。

  • java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
  • 大小: 8.3 KB
  • java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
  • 大小: 9.8 KB
  • java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
  • 大小: 8.7 KB
  • java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
  • 大小: 7.5 KB
  • java并发之ThreadPoolExecutor分析
            
    
    博客分类: 并发 并发ThreadPoolExecutor原理
  • 大小: 12.7 KB