tomcat优化线程池的思路真的好,每个javaer都应该了解一下

文章介绍了池化技术的概念,特别是线程池在Java和Tomcat中的应用。Java的ThreadPoolExecutor允许设定核心和最大线程数、任务队列和拒绝策略。Tomcat则在原生线程池基础上进行了优化,当线程池满负荷时,尝试先将任务放入队列,增加处理请求的机会,尤其适用于短暂的任务,从而提高并发处理能力。线程池的配置应根据业务需求进行调整,以平衡资源消耗和性能。
摘要由CSDN通过智能技术生成

池化技术

池化技术是一种常用的提升性能的手段,比如常见的数据库连接池、JAVA字符串的常量池、以及线程池等。

以JAVA中的线程为例,创建一个新线程的背后,其实是调用操作系统的api,消耗宝贵的系统资源,去执行一些业务逻辑。

如果我们频繁的创建销毁线程对象,对于CPU、内存都是一个很大的负担。

所以,线程池就应运而生了。本质上,池化技术是一种以空间换时间的做法。

线程池就是提前创建好一些线程供我们使用,使用完毕之后并不销毁这些线程,而是放回池中,等待下次继续使用。

而tomcat作为web界的容器一哥,自然也使用了线程池这种优化手段,为了提高业务处理能力和支持更高的并发,tomcat还对线程池做了更进一步的优化。

原生线程池

讲到tomcat优化线程池之前,我们先回顾一下JAVA原生的线程池。

JAVA原生线程池ThreadPoolExecutor位于java.util.concurrent包下。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

可以看到,ThreadPoolExecutor提供了7个参数。

其工作原理大致如下:

1,当线程池收到一个新任务,先判断当前线程数是否大于corePoolSize,如果小于corePoolSize,就新建一个线程执行任务。

2,当线程池中的线程数已经达到corePoolSize,再来新的任务就不会新建线程了,而是将任务投递到workQueue中。当核心线程有空闲了,就会从workQueue队列poll任务去执行。

3,如果任务非常多,workQueue已经达到最大任务数量了。这时maximumPoolSize就会发挥作用了。线程池会继续创建新线程用于执行任务,直至最大线程数达到maximumPoolSize。

4,如果任务继续增加,所有线程都满负荷工作了,队列也满了。此时线程池就会开始执行拒绝策略了,就是我们定义的handler。

5,当任务高峰期过去了,临时线程闲置下来了,此时线程池就会从workQueue中拉取任务继续执行了。临时线程使用poll(keepAliveTime, unit)方法拉取任务,如果在指定的unit时间内未获取到任务,临时线程就会被销毁回收。

除了默认的线程池以外,JAVA还提供了一些定制化的线程池,比如FixedThreadPool、CachedThreadPool等。

其实就是给默认的线程池设置了一些特殊的参数,以满足不同场景下的业务需要,这里就不过多介绍了,感兴趣的朋友可以通过源码去了解一下。

tomcat的线程池

通过对JAVA原生线程池的介绍,我们可以知道,当达到最大线程数时,表示线程池已经满负荷运行,无法再接收任务了。此时会执行线程池的拒绝策略,比如抛出异常、丢弃任务等。

那tomcat做了什么优化手段来提升并发量呢?

其实原理很简单:当总线程数达到最大线程maximumPoolSize时,不是立刻执行拒绝策略。而是先尝试将任务投递到任务队列中,如果任务队列此时仍然是满的,再执行拒绝策略。

如上图所示,就是tomcat线程池的execute方法。

如果我们点进executeInternal方法,就会发现其实现代码和JAVA原生线程池的execute方法是一样的,tomcat只是将原生执行方法包装了一层。

tomcat在外层catch了RejectedExecutionException异常,当异常抛出时,表示任务已满需要执行拒绝策略了。此时tomcat尝试将任务再次投递到任务队列,如果投递失败,再抛出一次RejectedExecutionException异常,转而去执行拒绝策略。

看到这,有的朋友可能会问了:任务都满了,再投递一次任务到队列中有什么用呢?

举个例子大家就明白了。

比如一个平稳运行的系统忽然遇到大量的流量涌入,但是这些请求可能大部分都是一些简单的CPU密集型任务,比如简单的计算、查询,并非耗时较长的IO任务。

一开始,tomcat默认的200个线程和10000的任务队列可能瞬间就被打满了,下一个任务进来时,由于线程池已经满负荷运行,可能就需要执行拒绝策略。

但是!这些简单的请求耗时是非常短的,可能几毫秒就已经任务完成。此时,任务队列已经有了可继续投递的剩余空间。

那么简单的一次继续投递任务队列的尝试,可能就会使本该抛异常的请求继续执行。那么反应到应用层面,就是服务器能继续正常处理请求,从而提升了服务器的并发处理能力。

另外,大家可以在execute方法中看到第一行的:submittedCount.incrementAndGet();

默认情况下,tomcat的任务队列TaskQueue的capacity是Integer.MAX_VALUE。

这样的话,当线程数达到核心线程数以后,再来新的任务都会被投递到任务队列中,就没有办法再创建新线程了,这样肯定是不行的。

所以,tomcat重写了LinkedBlockingQueue的offer方法,如果当前提交的任务数submittedCount大于核心线程数,并且小于最大线程数的情况下,就去创建一个新线程。

这样,就避免了无限往队列中投递任务的情况。

总结一下就是,对于一个新任务,有空闲的线程就先用空闲的线程,线程不够用又没达到上限就去创建新线程,线程达到上限就扔到队列排队去,队列满了执行重试机制,重试失败那就没办法了,执行拒绝策略吧。

最后

对于一个web容器来说,首先要考虑的自然就是性能,而性能指标又是通过什么来决定呢?

CPU和内存。

我们知道,每创建一个线程都是要消耗内存的(大约1M左右),如果无限制的创建线程势必会造成内存的浪费。

而对于CPU密集型的任务来说,过多的线程会造成CPU频繁的上下文切换,反而会降低服务器的性能。

所以,线程数并非是越大越好,应该根据具体的业务进行压测从而配置适当的参数。

在Springboot中,可以通过server.tomcat.max-threads/min-spare-threads/max-connections来修改线程池的默认配置。

finally,感谢您的点赞和关注。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员拾山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值