当我们在做一个Web 服务器迁移是,遇到一个问题。由于迁移的服务流量不定。峰值和谷值相差比较大。为了满足峰值的需要,当前服务器的minThreads 和maxThreads 都设置成了一致,为400. 这样的配置就相当于配置了FixedThreadPool。
- 好处是可以满足最大高峰的流量。
- 坏处就是线程资源在低峰时候有些浪费,系统的线程太多,对CPU 的负载也比较大。
那么我们是不是要和原来的min, max Thread 的配置一样哪。意见并不统一,还有人以JDK线程池举例,认为应该min和max 设置成一样。那么我们就需要对JDK 和 Tomcat的线程池实现进行了解。
JDK线程池原理
JDK线程池也是一个producer-consumer 模型。他的原理如下:
针对这个模型JDK的实现是ThreadPoolExecutor 这个类。
ThreadPoolExecutor 3 个最重要的参数:
corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。
上图是他的运行模式,基本可以归纳为如下:
从上图可知,从min 到max thread必须经过Queue满。 这样很多线程就积压在那里,不符合web server 服务更多请求的需求。因此Tomcat进行了修改。
Tomcat线程池原理
Tomcat 线程池JDK 线程池稍有不同,在到达corePoolSize后,先不进入Queue,而是达到最大线程数。然后尝试放入Queue里, 如果再失败,才会运行拒绝策略。
核心代码在TaskQueue。 它继承了非阻塞无界队列 LinkedBlockingQueue<Runnable> 并重写了的 offer 方法:
@Override
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) return super.offer(o);
//we are maxed out on threads, simply queue the object
if (parent.getPoolSize() == parent.getMaximumPoolSize()){
return super.offer(o);
}
//we have idle threads, just add it to the queue
if (parent.getSubmittedCount()<=(parent.getPoolSize())) {
return super.offer(o);
}
//if we have less threads than maximum force creation of a new thread
if (parent.getPoolSize()<parent.getMaximumPoolSize()) {
return false;
}
//if we reached here, we need to add it to the queue
return super.offer(o);
}
由于下面这个if 条件的存在,走到这个 if 条件的时候,提交的线程数没有到达核心线程数时,返回一个 false 标明,表示任务添加到阻塞队列失败。线程池就会认为阻塞队列已经无法继续添加任务到队列中了,根据默认线程池的工作逻辑,线程池就会创建新的线程直到最大线程数。
if (parent.getPoolSize()<parent.getMaximumPoolSize()) {
return false;
}
当然, tomcat 如果线程不满,会保持最小线程数运行。
参数的配置
了解了Tomcat运行的原理,我们就可以知道, 对于Tomcat 线程池,是不需要min =max 这种固定线程池配置的。它具有很好的伸缩性,当请求增多时,是可以开始增加线程直到最大,但是请求减少时,是可以缩小保证占用资源最小。
因此我们就配置了 min =200, max =400这样的配置。上线之后表现良好。