tomcat学习之三:tomcat的线程模型

本文详细探讨了Tomcat的线程模型,从Java线程基础出发,深入到Tomcat内部组件如StandardService的Executor、Connector、ProtocolHandler、JIoEndpoint以及startStopExecutor的角色。分析了Tomcat在启动过程中的线程结构和并发处理能力,为理解其高效处理HTTP请求提供了清晰的视角。

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

      tomcat作为最受欢迎的web服务器之一,他肯定是支持并发连接以及处理请求的。

1、java线程

       在开始分析tomcat线程模型前,简单回顾一下java的多线程编程。在java中。创建一个多线程程序可以直接实现Runnable接口或者继承Thread类并覆盖run方法。如下所示:
public class MyThread extends Thread {

	@Override
	public void run() {
		//TODO
	}	
}
       然后调用对象的start()方法启动线程,但由于java是单继承体系,所以一般通过实现Runnable接口来编写多线程。你如果想线程执行完毕时有一个返回结果,那么可以实现Callable接口,并覆盖call方法。
      但这样做,线程的启动以及停止等都需要程序员来完成,所以可以使用线程池来统一启动、停止线程和管理。 这也就是java的Executor类,它是一个接口,存在于java.util.concurrent包中,可以使用以下代码创建一个线程池:
ExecutorService exec = Executors.newCachedThreadPool();
      点开newCachedThreadPool()方法:
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
      可以看到最终使用了ThreadPoolExecutor类来创建,其中ExecutorService是Executor的子接口,而ThreadPoolExecutor是Executor的一个实现类。ThreadPoolExecutor的第一个参数表示要保持多少个线程在线程池中,参数2表示线程池中最多能有多少个线程,如果超出这个值,新加入的线程将会被放到最后一个参数Queue中去,第3以及第4个参数表示一个超时时间。
     简单回顾到这儿,但请记住Executor、ThreadPoolExecutor类以及ThreadPoolExecutor的参数1以及参数2。

2、tomcat中的多线程

2.1、StandardService中的Executor
        tomcat运行时会创建一个Server实例,而Server实例会启动多个Service,Service中的Connector用于接收、解析请求并交给Service中的Container处理请求。在StandardService中定义连接器以及容器代码如下:
protected Connector connectors[] = new Connector[0];
private final Object connectorsLock = new Object();
    
    /**
     * 
     */
protected ArrayList<Executor> executors = new ArrayList<Executor>();
       可以看到在容器的定义下方有一个Executor的定义,但这个Executor并不是上面提到的Executor,它是java.util.concurrent.Executor的一个子接口(为了易于表达,tomcat中的Executor简称为Executor,java自带的称为java的Executor),同样的,tomcat还有一个ThreadPoolExecutor实现了相应的java.util.concurrent.ThreadPoolExecutor.同时StandardService中也有相应的getExecutor,addExecutor、findExecutor方法。
       全文搜索addExecutor方法,可以发现在org.apache.catalina.mbean.ServiceMBean的addExecutor方法中,Service的addExecutor被调用了。打开Executor类结构,它只有一个实现类即StandardThreadExecutor,可以注意到这个类中持有一个ThreadPoolExecutor的实例。
 /**
  * The executor we use for this component
  */
protected ThreadPoolExecutor executor = null;
       由于tomcat中使用了Lifecycle来管理生命周期,所以,该接口的几个生命周期方法被调用时都会先调用相应的xxxInternal方法,例如,start()方法调用时会先调用startInternal方法,init方法调用时会先调用initInternal方法,点开该类的startInternal方法中可以看到ThreadPoolExecutor被初始化了,代码如下:
@Override
protected void startInternal() throws LifecycleException {
    
    taskqueue = new TaskQueue(maxQueueSize);
    TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
    executor.setThreadRenewalDelay(threadRenewalDelay);
    if (prestartminSpareThreads) {
        executor.prestartAllCoreThreads();
    }
    taskqueue.setParent(executor);

    setState(LifecycleState.STARTING);
}
      可以看到,这一个线程池中线程的数量由getMinSpareThreads()与getMaxThreads()决定,而该类中关于这两个属性如下:
/**
 * max number of threads
 */
protected int maxThreads = 200;
    
/**
 * min number of threads
 */
protected int minSpareThreads = 25;
      所以可以得出结论,如果在server.xml中配置了Executor标签,以及配置了Executor的maxThreads以及minSpareThreads属性,那么StandardSerivce中Executor的线程数量由此决定,如果没有配置,则默认数量区间为25 ~ 200,当然如果server.xml标签中没有配置Executor标签,那么StandardService中executors的size为0.
      可能你已经发现了,StandardService只是负责Executor的启动,添加等操作,翻遍整个类,都没有找到Executor被使用的方法。这儿可以先记住StandardService中有一组Executor,同时也记住Executor中线程数量在25 ~ 200之间。
2.2、Connector
      Connector组件作用为接收请求,解析请求以及将请求交由容器处理,但tomcat7版本中的Connector并没有亲自处理,而是委托给了ProtocolHandler处理,点开ProtocolHandler的类结构:


     从图上可以看到tomcat提供了3中ProtocolHandler,这3中处理请求区别如下:
     Http11Protocol:即使用传统的java IO来处理请求,会为每一个请求开启一条线程来处理。这样开销比较大,支持的并发数也比较少,tomcat7以下默认是使用这个类来处理Http请求。
     Http11NioProtocol:使用java 1.4以后的NIO来处理请求,NIO是基于缓存的、非阻塞的方式,同时支持更高的并发,tomcat8中默认使用NIO处理。关于NIO相比BIO(传统IO)的更多优势这儿就不在一一介绍了,有兴趣的可以百度。
     Http11AprProtocol:简单理解,就是从操作系统级别解决异步IO问题,大幅度的提高服务器的处理和响应性能, 也是Tomcat运行高并发应用的首选模式。

     所以Connector采用哪一种接收请求处理方式会在极大程度上影响tomcat的并发性。那么Connector到底使用哪一种方式呢?这取决于配置,下面是Connector的的构造函数:
public Connector(String protocol) {
    setProtocol(protocol);
    // Instantiate protocol handler
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        this.protocolHandler = (ProtocolHandler) clazz.newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    }
}
     这儿可以看出在Connector中使用反射创建了一个ProtocolHandler的实例对象,这个实例属于哪个类由protocolHandlerClassName决定,这儿点开setProtocol方法:
public void setProtocol(String protocol) {

     if (AprLifecycleListener.isAprAvailable()) {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11AprProtocol");
        } else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.ajp.AjpAprProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        } else {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11AprProtocol");
        }
     } else {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
             
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值