Tomcat源码分析-线程池应用

           记得前两年刚去公司实习那会儿,从老大学习的第一个终身受益技能是,分析错误堆栈和线程调用栈,正是这点,发现tomcat启动后线程数只有6个到第一次访问后新增了10个线程数这个变化让我很迷惑,于是开始了我的答疑解惑之旅。
         1、监听8080端口接收请求线程的类是哪个?什么时期启动的?大概逻辑是什么?
         大家都知道在发送http请求时,首先会进行tcp的三次握手,握手成功之后建立连接发送数据,因此该过程是在TCP协议对应抽象出来的类中,即Tomcat包(org.apache.tomcat.util.net)中的JIoEndpoint类。
         在初始化连接器(Connector)时,会最终初始化JIoEndpoint类,而该类的初始化其实就完成了监听请求线程数的设置,最大连接数的设置,还有服务端ServerSocket的实例化,这些设置实则是通过其父类AbstractEndpoint在初始化时回调自己bind方法完成的。
         而该线程真正被创建是在连接器(Connector)启动的过程中,连接器是在tomcat大多数组件启动完后比较靠后才启动的,连接器启动最终会调用JIoEndpoint的startInternal方法,在在该方法中,会根据自身初始化时的设置信息实例化一个线程池,如该线程池已初始化则无需在实例化,之后在启动监听线程,启动async timeout thread的线程。该线程池是存放处理具体任务线程的容器,该断代码如下
        
           
        2、数据是如何从接受线程移交到处理线程的?线程池是如何实现的?
         回答这个问题并不难,只是有些细节并不懂,想一探究竟。
         当有请求到来时,tomcat会对拿到socket进行一层封装,封装成 SocketWrapper,之后会把SocketWrapper以组合的方式嵌入到任务 SocketProcessor中,这样以任务的形式就可以把数据带到新的线程中处理,代码如下:
         
        
         之后则是线程池具体是如何执行或者分配该任务的。
         首先计算线程池中的线程数是否小于核心线程数(corePoolSize),核心线程数是在实例化线程池对象时设置的,ThreadPoolExecutor中的execute代码如下:
             
          如果当前线程数小于核心线程数(在tomcat中该值默认是10),则走addWorker逻辑 ;在addWorker方法中,首先会根据线程池状态,任务对象,任务队列这些因素做合法性检查,检查ok才对线程池中已有线程数与核心线程数据做对比,小于才会真正走后面产生线程,执行任务的逻辑,这部分代码如下:
          
         break retry后又把任务封装到一个新的任务中,与前面任务不同的是,该任务绑定了一个线程,并随Worker的产生而产生,它与线程池中任务队列中的任务是不同定位的,该对象的定位是线程池管理的线程,该对象数据结构如下:
        
         线程池通过一个HashSet<Worker>的数据结构存放已有的线程,因此线程池的池子可以指这个,新产生的线程会随着Worker存到set中,代码如下:
        
         
       进入到了addWorker这个方法,说明此时的任务队列是空的,当时又出现了一个疑问,如下
       
          3、线程池开启一个线程后,到底是在哪一步操作让它处于等待状态的,而不是直接结束掉?
         t.start()方法实则会开启一个线程调用了Worker中的run方法,查看这段代码不难发现,如当前处理的任务不为空或者能从任务队列中取到任务时,则在直接调用任务中的run方法进行真正业务处理,否则队列就会阻塞,代码如下:
         
        
        4、当线程池中的线程数已达到coreThreadNumber值时,任务队列中又没有任务,此时又来一个请求,请求是如何转到其中一个线程处理的?
         这个问题的答案其实已经明确了,当任务队列中有任务时,则会唤醒其中任意一个线程,如下是队列实现阻塞与唤醒的逻辑。
         
        5、为什么在tomcat启动后的第一次请求时常新增10个线程 ?
       这其实是在tomcat中的SocketProcessor(JIoEndpoint内部类)逻辑处理的,当socketwrapper被处理完后,状态是OPEN,UPGRADING,UPGRADING_TOMCAT,UPGRADED状态之一,则会继续被包装成一个SocketProcessor丢到线程池中进行处理,代码如下:
       
         用360浏览器第一次访问时会新增10个,而用edge浏览器访问有时则新增2个或4个,其实都是根据上述逻辑来决定的。
         
        6、在HostConfig发布同种类型的应用时也会用到线程池,为何过一会儿这些线程消亡了,而第一次请求后创建的线程却一直还存活着?
         其实查阅代码后不难发现,在HostConfig里面实例化线程池的时候多了一个设置,代码如下:
         
     
       该属性默认是false的,当该属性为是时,则从队列中取值用的是非阻塞方法,此时线程不会阻塞,会执行完毕而结束掉。

        这个场景典型的体现生产者消费者模式设计,生产者指接收线程,它把接收到的请求封装成特定的任务丢到线程池中,消费者指的是处理这些任务的线程,中间有一个存放任务的容器来协同两者之间的工作。
       通过寻找这些问题的答案,其实就很清楚了线程池是如何工作的,以及tomcat中是如何运用线程池的,本篇线程池的内容是围绕着这几个问题来的,并未对线程池做完整逻辑做一一解读。
       线程池里面还是有很多内容的,比如通过用一个原子变量的高位表示状态,低位表示可以拥有的最大容量,用了CAS的方式做自加等,网上有不少关于线程池的好文详细分析过了,剩下的逻辑就不在在此赘述了。
       
       线程池相关文章
       ctl变量的解释

       
 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值