版本:tomcat-embed-core-10.1.8.jar
前言
最近看到篇关于 Tomcat 线程池的博客,因为之前只看过 JDK 线程池,记录一下。在微服务横行的今天,确实还是有必要研究研究 Tomcat 的线程池
Tomcat 线程池和 JDK 线程池最大的不同就是它先把最大线程数用完,然后再提交任务到队列里面。强烈建议先弄懂 JDK 线程池原理再看本文,推荐阅读:深入理解线程池(ThreadPoolExecutor)——细致入微的讲源码。_if (workeradded) { t.start(); workerstarted = true-优快云博客
Tomcat 线程池
先打开 tomcat 的 server.xml 看一下,发现 tomcat 支持配置最大和最小线程数:
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
可配置的参数的中文说明如下:
这里主要关注线程池实现类,默认为 org.apache.catalina.core.StandardThreadExecutor
org.apache.catalina.core.StandardThreadExecutor#startInternal
/**
* Start the component and implement the requirements of
* {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {
// 创建 Tomcat 自定义的任务队列
taskqueue = new TaskQueue(maxQueueSize);
// 创建线程工厂
TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, getThreadPriority());
// 创建线程池
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,
taskqueue, tf);
executor.setThreadRenewalDelay(threadRenewalDelay);
// 最关键的地方,没有这行代码,Tomcat 的线程池则会表现的和 JDK 的线程池一样
taskqueue.setParent(executor);
setState(LifecycleState.STARTING);
}
这段方法就是构建线程池的地方,关键参数如下:
org.apache.tomcat.util.threads.ThreadPoolExecutor
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0) {
throw new IllegalArgumentException();
}
if (workQueue == null || threadFactory == null || handler == null) {
throw new NullPointerException();
}
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
prestartAllCoreThreads();
}
/**
* Starts all core threads, causing them to idly wait for work. This
* overrides the default policy of starting core threads only when
* new tasks are executed.
*
* @return the number of threads started
*/
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true)) {
++n;
}
return n;
}
}
tomcat 的线程池的名字和 jdk 线程池的名字一样,千万别搞混了。从构造函数可以看到,tomcat 线程池在创建完成后会调用 prestartAllCoreThreads
方法对所有核心线程做一次预热
线程池执行
private void executeInternal(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) {
return;
}
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) {
reject(command);
} else if (workerCountOf(recheck) == 0) {
addWorker(null, false);
}
}
else if (!addWorker(command, false)) {
reject(command);
}
}
查看 tomcat 线程池的 execute 方法,感觉非常眼熟,这不就是 jdk 线程池的代码吗?其实这里是在 workQueue#offer 做了文章
org.apache.tomcat.util.threads.TaskQueue
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
@Override
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) { // 如果parent为空,和JDK线程池没区别
return super.offer(o);
}
//we are maxed out on threads, simply queue the object
if (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) { // 运行中的线程数等于最大线程数,任务入队
return super.offer(o);
}
//we have idle threads, just add it to the queue
if (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) { // 未完成线程数 <= 运行中的线程数,任务入队
return super.offer(o);
}
//if we have less threads than maximum force creation of a new thread
if (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) { // 运行中的线程数量 < 最大线程数,返回失败,让它去创建线程
return false;
}
//if we reached here, we need to add it to the queue
return super.offer(o);
}
}
总结
本文主要介绍了 Tomcat 线程池的运行流程,和 JDK 线程池的流程比起来,它确实不一样。
而 Tomcat 线程池为什么要这样做呢?其实就是因为 Tomcat 处理的多是 IO 密集型任务,用户在前面等着响应呢,结果你明明还能处理,却让用户的请求入队等待?这样不好。说到底,又回到了任务类型是 IO 密集型还是 CPU 密集型这个话题上来,比起去改 JDK 的线程池参数,Tomcat 线程池又给了我们一个新的思路