首页 > 其他分享 >Tomcat 线程池学习总结

Tomcat 线程池学习总结

时间:2022-11-14 22:58:42浏览次数:58  
标签:总结 Tomcat command util threads org apache 线程

前提

Tomcat 10.1.x

Tomcat线程池介绍

Tomcat线程池,源于JAVA JDK自带线程池。由于JAVA JDK线程池策略,比较适合处理 CPU 密集型任务,但是对于 I/O 密集型任务,如数据库查询,rpc 请求调用等,不是很友好,所以Tomcat在其基础上进行了扩展。

任务处理流程

扩展线程池相关源码简析

Tomcat中定义了一个StandardThreadExecutor类,该类实现了org.apache.catalina.Executororg.apache.tomcat.util.threads.ResizableExecutor接口

该类内部定义了namePrefix(创建的线程名称前缀,默认值tomcat-exec-),maxThreads(最大线程数,默认值 200),minSpareThreads(最小线程数,即核心线程数,默认值 25),maxIdleTime(线程最大空闲时间,毫秒为单位,默认值60秒),maxQueueSize(最大队列大小,默认值 Integer.MAX_VALUE)等属性,此外,还定义了一个org.apache.tomcat.util.threads.ThreadPoolExecutor类型的执行器对象,一个execute(Runnable command) 方法

execute(Runnable command) 方法被调用时,会调用上述ThreadPoolExecutor类对象的execute方法

org.apache.catalina.core.StandardThreadExecutor.java

import org.apache.catalina.Executor;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.threads.ResizableExecutor;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;

public class StandardThreadExecutor extends LifecycleMBeanBase
        implements Executor, ResizableExecutor {

    protected static final StringManager sm = StringManager.getManager(StandardThreadExecutor.class);

    // ---------------------------------------------- Properties
    /**
     * Default thread priority
     */
    protected int threadPriority = Thread.NORM_PRIORITY;

    /**
     * Run threads in daemon or non-daemon state
     */
    protected boolean daemon = true;

    /**
     * Default name prefix for the thread name
     */
    protected String namePrefix = "tomcat-exec-";

    /**
     * max number of threads
     */
    protected int maxThreads = 200;

    /**
     * min number of threads
     */
    protected int minSpareThreads = 25;

    /**
     * idle time in milliseconds
     */
    protected int maxIdleTime = 60000;

    /**
     * The executor we use for this component
     */
    protected ThreadPoolExecutor executor = null;

    /**
     * the name of this thread pool
     */
    protected String name;

    /**
     * The maximum number of elements that can queue up before we reject them
     */
    protected int maxQueueSize = Integer.MAX_VALUE;

    /**
     * After a context is stopped, threads in the pool are renewed. To avoid
     * renewing all threads at the same time, this delay is observed between 2
     * threads being renewed.
     */
    protected long threadRenewalDelay =
        org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;

    private TaskQueue taskqueue = null;
    // ---------------------------------------------- Constructors
    public StandardThreadExecutor() {
        //empty constructor for the digester
    }
    
    //....此处代码已省略

    @Override
    public void execute(Runnable command) {
        if (executor != null) {
            // Note any RejectedExecutionException due to the use of TaskQueue
            // will be handled by the o.a.t.u.threads.ThreadPoolExecutor
            executor.execute(command);
        } else {
            throw new IllegalStateException(sm.getString("standardThreadExecutor.notStarted"));
        }
    }
    
    //....此处代码已省略

}

org.apache.tomcat.util.threads.ThreadPoolExecuto类对象的execute(Runnable command) 方法被调用时,会调用该类定义的一个executeInternal方法,并在捕获到RejectedExecutionException异常时,尝试再次将任务放入工作队列中。

executeInternal方法中,通过代码可知,当前线程数小于核心线程池大小时,会创建新线程,否则,会调用workQueue对象(org.apache.tomcat.util.threads.TaskQueue类型)的offer方法,将任务进行排队。Tomcat通过控制workQueue.offer()方法的返回值,实现了当前线程数超过核心线程池大小时,优先创建线程,而不是让任务排队。

org.apache.tomcat.util.threads.ThreadPoolExecutor

public class ThreadPoolExecutor extends AbstractExecutorService {
    //...此处代码已省略
    @Override
    public void execute(Runnable command) {
        submittedCount.incrementAndGet();
        try {
            executeInternal(command);
        } catch (RejectedExecutionException rx) {
            if (getQueue() instanceof TaskQueue) {
                // If the Executor is close to maximum pool size, concurrent
                // calls to execute() may result (due to Tomcat's use of
                // TaskQueue) in some tasks being rejected rather than queued.
                // If this happens, add them to the queue.
                final TaskQueue queue = (TaskQueue) getQueue();
                if (!queue.force(command)) {
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                }
            } else {
                submittedCount.decrementAndGet();
                throw rx;
            }
        }
    }


    /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@link RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    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)) { //workQueue.offer(command)为false时,会走以下的else if分支,创建线程
            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);
        }
    }
    
    //...此处代码已省略

}

org.apache.tomcat.util.threads.TaskQueue继承于java.util.concurrent.LinkedBlockingQueue,并重写了offer(排队任务的方法),该方法中,当当前线程数大于核心线程数,小于最大线程数时,返回false,导致上述executeInternal方法中workQueue.offer(command)false,进而导致该分支代码不被执行,执行addWorker(command, false)方法,创建新线程。

org.apache.tomcat.util.threads.TaskQueue

import java.util.Collection;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

import org.apache.tomcat.util.res.StringManager;

/**
 * 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 won't be able to force items onto the queue
 * itself.
 */
public class TaskQueue extends LinkedBlockingQueue<Runnable> {

    //...此处代码已省略
    
    /**
     * Used to add a task to the queue if the task has been rejected by the Executor.
     *
     * @param o         The task to add to the queue
     *
     * @return          {@code true} if the task was added to the queue,
     *                      otherwise {@code false}
     */
    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
    }


    @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);
    }
    //...此处代码已省略
}

参考链接

https://gitee.com/apache/tomcat/blob/10.1.x/java/org/apache/catalina/core/StandardThreadExecutor.java

标签:总结,Tomcat,command,util,threads,org,apache,线程
From: https://www.cnblogs.com/shouke/p/16890819.html

相关文章

  • python学习第六周总结
    封装封装:就是将数据和功能'封装'起来隐藏:在类的定义阶段名字前面使用两个下划线表示隐藏。就是将数据和功能隐藏起来不让用户直接调用,而是开发一些接口间接调用,从而可......
  • 面向对象周总结
    面向对象之三大特性面向对象三大特性之封装封装:就是将数据和功能'封装'起来隐藏:将数据和功能隐藏起来不让用户直接调用而是开发一些接口间接调用从而可以在接口内......
  • 线程的同步与互斥
    生产者消费者问题编译指令gcc-opcpc.c-lpthread#include"csapp.h"#include<pthread.h>#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<ti......
  • 线程概念学习笔记
    1.进程和线程1.1什么叫做线程定义:线程是CPU的基本调度单位,每个线程执行的都是进程代码的某个片段。实例演示:用活动监视器的CPU栏目看java进程的线程数的变化左边进......
  • python 多进程 多线程 协程
    多进程-进程池1fromconcurrent.futuresimportProcessPoolExecutor23withProcessPoolExecutor(max_workers=10)asexecutor:4results=executor.map......
  • 关于函数调用的总结
    首先强调的是:C语言中所有函数调用都是传值调用(数组是例外情况。C里数组名就表示了数组的首地址当把数组名当参数传递时,其实也就是传了个地址而已)1、传值调用......
  • 周总结7
    面向对象之封装,多态,反射派生实际应用importdatetimeimportjsonimportdatetimeimportjsonclassMyJsonEncoder(json.JSONEncoder):defdefault(s......
  • 51st 2022/11/12 模拟赛总结36
    这次按自己的话来说,不能接受因为和估分差距有点大赛时很开心地以为能A两题,一题50然后爆成120原因:T1的100->20现发现T1是因为没有全取模,很失落其实是因为考试时的一......
  • C++初阶(命名空间+缺省参数+const总结+引用总结+内联函数+auto关键字)
    命名空间概述在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的......
  • Pthread 并发编程(二)——自底向上深入理解线程
    Pthread并发编程(二)——自底向上深入理解线程前言在本篇文章当中主要给大家介绍线程最基本的组成元素,以及在pthread当中给我们提供的一些线程的基本机制,因为很多语言的......