【线程池】Tomcat线程池

版本: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 线程池又给了我们一个新的思路

参考

每天都在用,但你知道 Tomcat 的线程池有多努力吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值