[置顶] tomcat5源码解析一 PoolTcpEndpoint类

PoolTcpEndpoint类负责创建监听http连接端口的serversocket,并将得到得连接交给线程池中的线程处理。

早些时候Tomcat使用的是一个线程监听,多个工作线程的模式:即一个线程专门负责响应连接请求,再递交给工作线程进行处理。Tomcat
5 后开始使用线程池取代了原有的方案。如下图所示:

早期的方式

新方式

那么单从结构来说,新的方式相对了老方法而言,消减了线程之间不必须的关联关系,使单个请求的处理流程完全独立。同时我们看到,在老方式里,一旦listener线程挂掉,那么整个tomcat也就完完了,这是非常危险的事。

下面就来看看这种工作模式, tomcat5是怎么实现的。

在tomcat5发布的时候,应该java jdk自带的threadpool类还没有出来,于是tomcat5自己实现了一个threadpool,但这个threadpool跟现在jdk的有很大的不同,它里面存储的是ControlRunnable类的实例。

下面先介绍tomcat5自己实现的一个继承Thread类的子类ThreadWithAttributes类。类如其名,它其实就是一个附带其他自定义属性的thread。

ControlRunnable类实现了runnable接口。它有一个指向theadpool实例的引用,这个就是管理它的线程池。一个指向threadwithattibutes类示例的引用,这个thread就是真正调用它的run方法的thread。一个指向ThreadPoolRunnable类的引用toRun,ThreadPoolRunnable真正指定了需要run的方法,因为ControlRunnable最终会调用threadpoolrunnable的runIt方法。

在ControlRunnable类的默认构造函数中,它把toRun设成null,把自己托管给threadpool,用它自己作为runnable的一个实例传给threadwithattibutes,然后调用threadwithattibutes的start方法,启动一个线程。因为这个时候它的shouldRun为false值,所以新起的线程在它的run方法中,它会wait。

回到刚刚的PoolTcpEndpoint类,它在startEndpoint方法中会首先调用threadpool的start方法,这个方法会生成很多的ControlRunnable实例,并保存在threadpool的pool数组中,这样大家可以想到,这会新生成很多线程,但这些线程都wait了,因为这个时候它们的runnable的shouldrun为false值。

然后在PoolTcpEndpoint的startEndpoint中,会创建一个TcpWorkerThread实例,并调用threadpool的runIt方法。threadpool的runIt方法,会从它管理的所有ControlRunnable中,找一个可用的,然后调用这个可用的ControlRunnable方法runIt方法。ControlRunnable的runIt方法很简单,它会将自己的toRun设为传入的ThreadPoolRunnable的实例,即刚刚的TcpWorkerThread实例,然后它会将shouldrun设为true,然后再调用notify,使刚刚wait住的自己重新活动起来。

重新活动起来的ControlRunnable会继续自己的run方法,调用toRun的RunIt方法,那么这个TcpWorkerThread实例到底是干什么用的呢。

这个TcpWorkerThread的RunIt方法就是用来接受连接,并处理的。它使用PoolTcpEndpoint的ServerSocket来进行accept操作,当接收到新的连接时,它会用自己作为参数再调用threadpool的runIt方法。重复在startEndPoint中的发生的事情(这样又可以accept新的请求。)。另一方面,它会对刚刚接收到的返回的socket进行连接处理。

tomcat5实现的线程池是直接管理runnable对象,然后新生成的线程会wait,直到runnable的shouRun为true,即某个表示task的实例变量(toRun)被赋值,才会被notify,然后运行,运行完后又会将shouldrun设为false,调用returnController将自己注册成空闲的线程便于到时候threadpool快速找到一个空闲的线程,然后循环,又会wait等待toRun被赋值。

说到这里不禁想去研究下jdk中的threadpool的实现,来与tomcat5 的实现比较一下。

jdk7中的threadpool的实现跟tomcat实现有一点相同,它对任务的表达也是使用一个实现了runnable接口的Worker类。这里不同的是jdk中的threadpool实现了一个任务队列,当任务的数量的数量大于corepoolsize的时候,新添加任务的会被加到等待队列中去。

然后我们看Work的run方法会调用threadpool的runWorker方法,它会循环 先查看自己的firsttask是否有值(这个只有在第一次work执行run方法的时候,会有值),或者队列中是否有任务(第二次以后都是检查这个值 getTask())。如果队列中有任务的话,就执行新的任务。如果队列中没有任务的话,runWork方法会阻塞(因为调用了队列的take方法),直到队列中有新的任务;不过这儿有两种情况意外:

1.如果现在thread数量超过corepoolsize的话(即这个thread可以看作是超过任务数量超过corepoolsize并且队列已满的情况下,调用addworker方法添加的),那么这getTask会返回null,于是跳出循环,thread最后会terminated

2. 如果用户设置了allowCoreThreadTimeOut的值,那么就算是core thread,那么在threadpool空闲的时候,getTask会返回null,跳出循环,最后terminated。

在jdk和tomcat的threadpool实现中,都是通过在runnable的方法里,去获取任务并执行,若当前没有任务,则阻塞自己。但是在tomcat的实现中是找到相应的runnable 并给予任务,然后notify这个runnable,所以需要维护每个runnable对象都有一个wait队列。但是在jdk中,runnable的阻塞与任务的获取都是由队列决定,只维护了任务队列的一个wait队列即可。


你可能感兴趣的:([置顶] tomcat5源码解析一 PoolTcpEndpoint类)