志在指尖
用双手敲打未来

原生线程池这么强大,Tomcat 为何还需扩展线程池?

Tomcat/Jetty是目前比较流行的Web容器,两者接受恳求之后都会转交给线程池处理,这样能够有用提高处理的能力与并发度。JDK提高完整线程池完成,可是Tomcat/Jetty都没有直接运用。Jetty选用自研方案,内部完成QueuedThreadPool线程池组件,而Tomcat选用扩展方案,踩在JDK线程池的肩膀上,扩展JDK原生线程池。
JDK原生线程池能够说功用比较完善,运用也比较简单,那为何Tomcat/Jetty却不选择这个方案,反而自己去动手完成那?
JDK线程池#
通常咱们能够将履行的使命分为两类:
cpu密集型使命
io密集型使命
cpu密集型使命,需求线程长时间进行的杂乱的运算,这种类型的使命需求少创立线程,过多的线程将会频频引起上文切换,下降使命处理处理速度。
而io密集型使命,因为线程并不是一向在运行,或许大部分时间在等候IO读取/写入数据,添加线程数量能够提高并发度,尽或许多处理使命。
JDK原生线程池作业流程如下:
线程池履行流程图
详情能够检查一文教你安全的关闭线程池,上图假定运用LinkedBlockingQueue。
魂灵拷问:上述流程是否记错过?在很长一段时间内,我都以为线程数量抵达最大线程数,才放入行列中。 ̄□ ̄||
上图中能够发现只需线程池线程数量大于中心线程数,就会先将使命加入到使命行列中,只需使命行列加入失利,才会再新建线程。也就是说原生线程池行列未满之前,最多只需中心线程数量线程。
这种策略明显比较合适处理cpu密集型使命,可是对于io密集型使命,如数据库查询,rpc恳求调用等,就不是很友好了。
因为Tomcat/Jetty需求处理很多客户端恳求使命,假如选用原生线程池,一旦接受恳求数量大于线程池中心线程数,这些恳求就会被放入到行列中,等候中心线程处理。这样做明显下降这些恳求总体处理速度,所以两者都没选用JDK原生线程池。
处理上面的办法能够像Jetty自己完成线程池组件,这样就能够更加适配内部逻辑,不过开发难度比较大,另一种就像Tomcat一样,扩展原生JDK线程池,完成比较简单。
下面首要以Tomcat扩展线程池,讲讲其完成原理。
扩展线程池#
首要咱们从JDK线程池源码出发,检查怎么这个基础上扩展。
能够看到线程池流程首要分为三步,第二步依据queue#offer办法回来成果,判别是否需求新建线程。
JDK原生行列类型LinkedBlockingQueue,SynchronousQueue,两者完成逻辑不尽相同。
LinkedBlockingQueue
offer办法内部将会依据行列是否已满作为判别条件。若行列已满,回来false,若行列未满,则将使命加入行列中,且回来true。
SynchronousQueue
这个行列比较特殊,内部不会储存任何数据。若有线程将使命放入其中将会被堵塞,直到其他线程将使命取出。反之,若无其他线程将使命放入其中,该行列取使命的办法也将会被堵塞,直到其他线程将使命放入。
对于offer办法来说,若有其他线程正在被取办法堵塞,该办法将会回来true。反之,offer办法将会回来false。
所以若想完成合适io密集型使命线程池,即优先新建线程处理使命,关键在于queue#offer办法。能够重写该办法内部逻辑,只需当前线程池数量小于最大线程数,该办法回来false,线程池新建线程处理。java
当然上述完成逻辑比较糙,下面咱们就从Tomcat源码检查其完成逻辑。
Tomcat扩展线程池#
Tomcat扩展线程池直接承继JDK线程池java.util.concurrent.ThreadPoolExecutor,重写部分办法的逻辑。别的还完成了TaskQueue,直接承继LinkedBlockingQueue,重写offer办法。
首要检查Tomcat线程池的运用办法。
能够看到Tomcat线程池运用办法与一般的线程池差不太多。
接着咱们检查一下Tomcat线程池中心办法execute的逻辑。
execute办法逻辑比较简单,使命中心还是交给Java原生线程池处理。这儿首要添加一个重试策略,假如原生线程池履行拒绝策略的状况,抛出RejectedExecutionException异常。这儿将会捕获,然后从头再次尝试将使命加入到TaskQueue,尽最大或许履行使命。
这儿需求注意submittedCount变量。这是Tomcat线程池内部一个重要的参数,它是一个AtomicInteger变量,将会实时计算已经提交到线程池中,但还没有履行结束的使命。也就是说submittedCount等于线程池行列中的使命数加上线程池作业线程正在履行的使命。TaskQueue#offer将会运用该参数完成相应的逻辑。
接着咱们首要检查TaskQueue#offer办法逻辑。
中心逻辑在于第三步,这儿假如submittedCount小于当前线程池线程数量,将会回来false。上面咱们讲到offer办法回来false,线程池将会直接创立新线程。
Dubbo2.6.X版本添加EagerThreadPool,其完成原理与Tomcat线程池差不多,感兴趣的小伙伴能够自行翻阅。
折衷办法#
上述扩展办法虽然看起不是很难,可是自己完成价值或许就比较大。若不想扩展线程池运行io密集型使命,能够选用下面这种折衷办法。
Copy
newThreadPoolExecutor(10,10,0L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue(100));
不过运用这种方法将会使keepAliveTime失效,线程一旦被创立,将会一向存在,比较糟蹋系统资源。
总结#
JDK完成线程池功用比较完善,可是比较合适运行CPU密集型使命,不合适IO密集型的使命。对于IO密集型使命能够直接经过设置线程池参数方法做到。

未经允许不得转载:IT技术网站 » 原生线程池这么强大,Tomcat 为何还需扩展线程池?
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

志在指尖 用双手敲打未来

登录/注册IT技术大全

热门IT技术

C#基础入门   SQL server数据库   系统SEO学习教程   WordPress小技巧   WordPress插件   脚本与源码下载