1. 首页 > 快讯

解决 Tomcat JDK 原生线程池 Bug

各位老铁们好,相信很多人对解决 Tomcat JDK 原生线程池 Bug都不是特别的了解,因此呢,今天就来为大家分享下关于解决 Tomcat JDK 原生线程池 Bug以及的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!

为提高处理能力和并发度,Web容器一般会把处理请求的任务放到线程池,而JDK的原生线程池先天适合CPU密集型任务,并不适合我们通常的 I/O 密集任务处理,于是Tomcat改造之。

Tomcat 线程池原理

其实ThreadPoolExecutor的参数主要有如下关键点:

  • 限制线程个数 

  • 限制队列长度

而Tomcat对这俩资源都需要限制,否则高并发下CPU、内存都有被耗尽可能。因此Tomcat的线程池传参:

  1. // 定制的任务队列 
  2. taskqueue = new TaskQueue(maxQueueSize); 
  3.  
  4. // 定制的线程工厂 
  5. TaskThreadFactory tf = new TaskThreadFactory(namePrefix, 
  6.                                daemon, 
  7.                                getThreadPriority() 
  8. ); 
  9.  
  10. // 定制线程池 
  11. executor = new ThreadPoolExecutor(getMinSpareThreads(), 
  12.                   getMaxThreads(), 
  13.                      maxIdleTime,  
  14.                      TimeUnit.MILLISECONDS, 
  15.                      taskqueue, 
  16.                      tf); 

Tomcat对线程数也有限制,设置:

  • 核心线程数(minSpareThreads)
  • 最大线程池数(maxThreads)

Tomcat线程池还有自己的特色任务处理流程,通过重写execute方法实现了自己的特色任务处理逻辑:

  1. 前corePoolSize个任务时,来一个任务就创建一个新线程
  2. 再有任务,就把任务放入任务队列,让所有线程去抢。若队列满,就创建临时线程
  3. 总线程数达到maximumPoolSize,则继续尝试把任务放入任务队列
  4. 若缓冲队列也满了,插入失败,执行拒绝策略

和 JDK 线程池的区别就在step3,Tomcat在线程总数达到最大数时,不是立即执行拒绝策略,而是再尝试向任务队列添加任务,添加失败后再执行拒绝策略。

具体又是如何实现的呢? 

  1. public void execute(Runnable command, long timeout, TimeUnit unit) { 
  2.     submittedCount.incrementAndGet(); 
  3.     try { 
  4.         // 调用JDK原生线程池的execute执行任务 
  5.         super.execute(command); 
  6.     } catch (RejectedExecutionException rx) { 
  7.        // 总线程数达到maximumPoolSize后,JDK原生线程池会执行默认拒绝策略 
  8.         if (super.getQueue() instanceof TaskQueue) { 
  9.             final TaskQueue queue = (TaskQueue)super.getQueue(); 
  10.             try { 
  11.                 // 继续尝试把任务放入任务队列 
  12.                 if (!queue.force(command, timeout, unit)) { 
  13.                     submittedCount.decrementAndGet(); 
  14.                     // 若缓冲队列还是满了,插入失败,执行拒绝策略。 
  15.                     throw new RejectedExecutionException("..."); 
  16.                 } 
  17.             }  
  18.         } 
  19.     } 

定制任务队列

Tomcat线程池的execute方法第一行:

  1. submittedCount.incrementAndGet(); 

任务执行失败,抛异常时,将该计数器减一:

  1. submittedCount.decrementAndGet(); 

Tomcat线程池使用 submittedCount 变量维护已提交到线程池,但未执行完的任务数量。

为何要维护这样一个变量呢?

Tomcat的任务队列TaskQueue扩展了JDK的LinkedBlockingQueue,Tomcat给了它一个capacity,传给父类LinkedBlockingQueue的构造器。

  1. public class TaskQueue extends LinkedBlockingQueue { 
  2.  
  3.   public TaskQueue(int capacity) { 
  4.       super(capacity); 
  5.   } 
  6.   ... 

capacity参数通过Tomcat的 maxQueueSize 参数设置,但maxQueueSize默认值为Integer.MAX_VALUE:这样,当前线程数达到核心线程数后,再来的任务,线程池会把任务添加到任务队列,并且总会成功,就永远无机会创建新线程了。

为此,TaskQueue重写了LinkedBlockingQueue#offer,在合适时机返回false,表示任务添加失败,线程池此时会创建新的线程。

什么叫合适时机?

  1. public class TaskQueue extends LinkedBlockingQueue { 
  2.  
  3.   ... 
  4.    @Override 
  5.   // 线程池调用任务队列的方法时,当前线程数 > core线程数 
  6.   public boolean offer(Runnable o) { 
  7.  
  8.       // 若线程数已达max,则不能创建新线程,只能放入任务队列 
  9.       if (parent.getPoolSize() == parent.getMaximumPoolSize())  
  10.           return super.offer(o); 
  11.            
  12.       // 至此,表明 max线程数 > 当前线程数 > core线程数 
  13.       // 说明可创建新线程: 
  14.        
  15.       // 1. 若已提交任务数 < 当前线程数 
  16.       //    表明还有空闲线程,无需创建新线程 
  17.       if (parent.getSubmittedCount()<=(parent.getPoolSize()))  
  18.           return super.offer(o); 
  19.            
  20.       // 2. 若已提交任务数 > 当前线程数 
  21.       //    线程不够用了,返回false去创建新线程 
  22.       if (parent.getPoolSize()
  23.           return false
  24.            
  25.       // 默认情况下总是把任务放入任务队列 
  26.       return super.offer(o); 
  27.   } 
  28.    

所以Tomcat维护 已提交任务数 是为了在任务队列长度无限时,让线程池还能有机会创建新线程。

 

用户评论

■□丶一切都无所谓

我记得之前也看过这个bug的讨论,说有点影响性能.

    有11位网友表示赞同!

执念,爱

Tomcat一直在更新优化吧,这bug修复得及时。

    有7位网友表示赞同!

抚涟i

51CTO的文章写的挺详细的,有图有真相确实可以参考下。

    有5位网友表示赞同!

追忆思域。

JDK原生线程池一直都是个热门话题!

    有15位网友表示赞同!

莫失莫忘

学习学习,这个Bug修复方法以后可以用上。

    有12位网友表示赞同!

寒山远黛

其实好多程序员都不知道这些底层的机制,还是蛮重要的。

    有16位网友表示赞同!

墨城烟柳

Tomcat太牛了,能发现并解决这样细微的BUG。

    有11位网友表示赞同!

あ浅浅の嘚僾

看这篇文章感觉自己的线程池知识还得加深!

    有13位网友表示赞同!

惯例

现在框架越来越强大,像tomcat这些老牌的组件真的需要不断迭代才能跟上时代

    有17位网友表示赞同!

七夏i

JDK原生线程池确实有时候不太好用,改进一下还是挺好的!

    有20位网友表示赞同!

青山暮雪

希望作者能多写一些这种深入的技术文章!

    有10位网友表示赞同!

不离我

学习编程真是一趟永无止境的旅程!

    有12位网友表示赞同!

夏至离别

对Java的底层机制有了更深的了解是很有帮助的!

    有9位网友表示赞同!

素婉纤尘

这篇文章真是太有价值了,我以后遇到类似问题还能参考。

    有16位网友表示赞同!

冷眼旁观i

感觉这种技术博客还挺实用的,能学到很多新知识!

    有13位网友表示赞同!

ゞ香草可樂ゞ草莓布丁

看标题就知道是针对Tomcat和JDK线程池的难题,值得一看!

    有15位网友表示赞同!

野兽之美

这篇文章可能对开发人员很有帮助! <

    有14位网友表示赞同!

一别经年

学习这些Bug修复方法确实可以提升自己的编程水平!

    有7位网友表示赞同!

孤岛晴空

51CTO上的文章质量一般还是比较高的!

    有16位网友表示赞同!

发呆

JDK的原生线程池一直都是一个很复杂的领域!

    有7位网友表示赞同!

本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.iotsj.com//kuaixun/7905.html

联系我们

在线咨询:点击这里给我发消息

微信号:666666