版本:tomcat-embed-core-10.1.8.jar
前言
最近面试被问到 Tomcat 线程池,因为之前只看过 JDK 线程池,没啥头绪。在微服务横行的今天,确实还是有必要研究研究 Tomcat 的线程池
Tomcat 线程池和 JDK 线程池最大的不同就是它先把最大线程数用完,然后再提交任务到队列里面。强烈建议先弄懂 JDK 线程池原理再看本文,推荐阅读:深入理解线程池(ThreadPoolExecutor)——细致入微的讲源码。_if (workeradded) { t.start(); workerstarted = true-CSDN博客
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
java">/*** 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
java">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
方法对所有核心线程做一次预热
线程池执行
java">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
java">public class TaskQueue extends LinkedBlockingQueue<Runnable> {
@Overridepublic boolean offer(Runnable o) {//we can't do any checksif (parent==null) { // 如果parent为空,和JDK线程池没区别return super.offer(o);}//we are maxed out on threads, simply queue the objectif (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) { // 运行中的线程数等于最大线程数,任务入队return super.offer(o);}//we have idle threads, just add it to the queueif (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) { // 未完成线程数 <= 运行中的线程数,任务入队return super.offer(o);}//if we have less threads than maximum force creation of a new threadif (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) { // 运行中的线程数量 < 最大线程数,返回失败,让它去创建线程return false;}//if we reached here, we need to add it to the queuereturn super.offer(o);}
}
总结
本文主要介绍了 Tomcat 线程池的运行流程,和 JDK 线程池的流程比起来,它确实不一样。
而 Tomcat 线程池为什么要这样做呢?其实就是因为 Tomcat 处理的多是 IO 密集型任务,用户在前面等着响应呢,结果你明明还能处理,却让用户的请求入队等待?这样不好。说到底,又回到了任务类型是 IO 密集型还是 CPU 密集型这个话题上来,比起去改 JDK 的线程池参数,Tomcat 线程池又给了我们一个新的思路
参考
每天都在用,但你知道 Tomcat 的线程池有多努力吗?