tomcat源码分析-executor及其实现StandardThreadExecutor

本文深入探讨了Tomcat中用于处理用户请求的StandardThreadExecutor。这是一个实现了Executor接口的类,它扩展了生命周期管理并自定义了线程池行为。StandardThreadExecutor使用TaskQueue,一个基于LinkedBlockingQueue的定制版本,来确保即使在无界队列的情况下,也能充分利用非核心线程。在启动和停止过程中,StandardThreadExecutor遵循特定的生命周期方法,如startInternal和stopInternal。通过重写offer方法,TaskQueue能够在需要时启动额外的线程,从而突破核心线程限制。这种设计提供了更灵活的线程池管理,同时保持了对外部调用的简单性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

executor

executor 是引擎,用户请求是经过executor进行处理的,知觉告诉我们这应该是一个多线程,线程池的容器,我们先从类的关系看下;

public interface Executor extends java.util.concurrent.Executor, Lifecycle {

    public String getName();

    @Deprecated
    void execute(Runnable command, long timeout, TimeUnit unit);
}

executor是个接口,并继承jdk的juc包下面的executor接口

StandardThreadExecutor属性

standardThreadExecutor是实现类,如图它也继承了生命周期相关的接口,受tomcat的生命周期管理。
在这里插入图片描述

public class StandardThreadExecutor extends LifecycleMBeanBase
        implements Executor, ResizableExecutor {

    protected static final StringManager sm = StringManager.getManager(StandardThreadExecutor.class);
    protected int threadPriority = Thread.NORM_PRIORITY;
    //默认守护线程,主线退出后,会自动退出,不会有残余
    protected boolean daemon = true;

     //线程名字的前缀
    protected String namePrefix = "tomcat-exec-";
    //默认最大线程200
    protected int maxThreads = 200;
    //(int)最小线程数(空闲和活动)始终保持活动状态,默认为 25
    protected int minSpareThreads = 25;
    //最长活跃时间 60秒
    protected int maxIdleTime = 60000;
     //注意这里的ThreadPoolExecutor 是tomcat实现的线程池,不是juc包下的
    protected ThreadPoolExecutor executor = null;
     //线程名称
    protected String name;
    protected boolean prestartminSpareThreads = false;
    //最大的队列大小
    protected int maxQueueSize = Integer.MAX_VALUE;
//(long)如果配置了ThreadLocalLeakPreventionListener,它将通知此执行程序有关已停止的上下文。上下文停止后,池中的线程将被更新。为避免同时更新所有线程,此选项在任意2个线程的续订之间设置延迟。该值以ms为单位,默认值为1000ms。如果值为负,则不会续订线程。 ¶ Lifecycle模板方法
    protected long threadRenewalDelay =
        org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;
// 任务队列
private TaskQueue taskqueue = null;

我们看下这个组件启动的方法,生命周期的startInternal()方法

 @Override
    protected void startInternal() throws LifecycleException {
       //自己实现的任务队列,后面讲下
        taskqueue = new TaskQueue(maxQueueSize);
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
        //核心线程25个,最大200个
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
        executor.setThreadRenewalDelay(threadRenewalDelay);
        if (prestartminSpareThreads) {
            executor.prestartAllCoreThreads();
        }
        taskqueue.setParent(executor);

        setState(LifecycleState.STARTING);
    }

stopInternal方法

@Override
protected void stopInternal() throws LifecycleException {

    setState(LifecycleState.STOPPING);
    if (executor != null) {
        executor.shutdownNow();
    }
    executor = null;
    taskqueue = null;
}
  • 核心executor方法
@Override
public void execute(Runnable command, long timeout, TimeUnit unit) {
    if (executor != null) {
        executor.execute(command,timeout,unit);
    } else {
        throw new IllegalStateException(sm.getString("standardThreadExecutor.notStarted"));
    }
}

@Override
public void execute(Runnable command) {
    if (executor != null) {
        try {
            executor.execute(command);
        } catch (RejectedExecutionException rx) {
            //there could have been contention around the queue
            if (!((TaskQueue) executor.getQueue()).force(command)) {
                throw new RejectedExecutionException(sm.getString("standardThreadExecutor.queueFull"));
            }
        }
    } else {
        throw new IllegalStateException(sm.getString("standardThreadExecutor.notStarted"));
    }
}

补充TaskQueue

我们知道工作队列是有TaskQueue保障的,它继承自LinkedBlockingQueue(一个阻塞的链表队列),来看下源代码吧。

/**
 * As task queue specifically designed to run with a thread pool executor. The
 * task queue is optimised to properly utilize threads within a thread pool
 * executor. If you use a normal queue, the executor will spawn threads when
 * there are idle threads and you wont be able to force items onto the queue
 * itself.
 */
public class TaskQueue extends LinkedBlockingQueue<Runnable> {

    private static final long serialVersionUID = 1L;
    protected static final StringManager sm = StringManager
            .getManager("org.apache.tomcat.util.threads.res");
    private static final int DEFAULT_FORCED_REMAINING_CAPACITY = -1;

    private transient volatile ThreadPoolExecutor parent = null;

    // No need to be volatile. This is written and read in a single thread
    // (when stopping a context and firing the listeners)
    private int forcedRemainingCapacity = -1;

    public TaskQueue() {
        super();
    }

    public TaskQueue(int capacity) {
        super(capacity);
    }

    public TaskQueue(Collection<? extends Runnable> c) {
        super(c);
    }

    public void setParent(ThreadPoolExecutor tp) {
        parent = tp;
    }

    public boolean force(Runnable o) {
        if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
        return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
    }

    public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
        if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
        return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected
    }

    @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);
    }

    @Override
    public Runnable poll(long timeout, TimeUnit unit)
            throws InterruptedException {
        Runnable runnable = super.poll(timeout, unit);
        if (runnable == null && parent != null) {
            // the poll timed out, it gives an opportunity to stop the current
            // thread if needed to avoid memory leaks.
            parent.stopCurrentThreadIfNeeded();
        }
        return runnable;
    }
    @Override
    public Runnable take() throws InterruptedException {
        if (parent != null && parent.currentThreadShouldBeStopped()) {
            return poll(parent.getKeepAliveTime(TimeUnit.MILLISECONDS),
                    TimeUnit.MILLISECONDS);
            // yes, this may return null (in case of timeout) which normally
            // does not occur with take()
            // but the ThreadPoolExecutor implementation allows this
        }
        return super.take();
    }
    @Override
    public int remainingCapacity() {
        if (forcedRemainingCapacity > DEFAULT_FORCED_REMAINING_CAPACITY) {
            // ThreadPoolExecutor.setCorePoolSize checks that
            // remainingCapacity==0 to allow to interrupt idle threads
            // I don't see why, but this hack allows to conform to this
            // "requirement"
            return forcedRemainingCapacity;
        }
        return super.remainingCapacity();
    }
    public void setForcedRemainingCapacity(int forcedRemainingCapacity) {
        this.forcedRemainingCapacity = forcedRemainingCapacity;
    }
    void resetForcedRemainingCapacity() {
        this.forcedRemainingCapacity = DEFAULT_FORCED_REMAINING_CAPACITY;
    }
}

让我们回忆下jdk线程池的增长过程:

  • 1.优先创建线程数量至核心线程数。当线程池中的数量小于coreSize,即使有核心线程空闲,也会创建核心线程来执行;
  • 2.达到核心线程数后。向队列扔任务,如果队列未满,添加任务,如果队列满了,创建非核心线程执行任务;如果队列和非核心线程满了,触发拒绝策略;

TaskQueue是无界队列,按照上述所讲任务永远不会满,一直添加任务,不会启动非核心线程。但是,其重写offer方法,当其线程池中线程大小小于maximumPoolSize的时候,返回false,如果返回fase,那么会创建线程执行任务,从何达到,即使无界队列也能突破核心线程的限制,增长线程到maxThreads,超过之后。
一句话,为了兼容所有任务,采用无界队列;传统的jdk无界队列,非核心线程永远利用不到,但是taskQueue可以做到采用无界队列,还能让非核心线程也能启动。所以TaskQueue是专门为线程池而设计的。

为什么不是直接使用ThreadPoolExecutor 这里你是否考虑过一个问题
为什么Tomcat会自己构造一个StandardThreadExecutor而不是直接使用ThreadPoolExecutor?
从上面的代码,你会发现这里只使用了execute的两个主要方法,它希望让调用层屏蔽掉ThreadPoolExecutor的其它方法:

  • 它体现的原则:最少知识原则: 只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少
  • 它体现的设计模式:结构型 - 外观(Facade) 外观模式(Facade pattern),它提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值