tomcat源码系列导航栏

tomcat源码分析环境搭建

tomcat启动过程-load初始化

tomcat启动过程-start启动

目录

前言

启动流程

启动入口main函数

代码块一 start

代码块二  StandardServer.startInternal()

代码块三 connector.startInternal()

代码块四 startAcceptorThreads

代码块五 acceptor.run()

代码块六 poller.run

总结


前言

上一篇文章简单分析了tomcat启动过程中load初始化,主要是模板设计模式,监听者模式,以及反射技术的应用。本篇文章,我们来分析一下start启动过程中,tomcat是如何使用线程池来做处理请求的。

启动流程

启动入口main函数

 public static void main(String args[]) {......Bootstrap dameon= new Bootstrap();try {String command = "start";......if (command.equals("start")) {daemon.setAwait(true);//load初始化daemon.load(args);//分析一 start启动daemon.start();if (null == daemon.getServer()) {System.exit(1);}} ......} catch (Throwable t) {// Unwrap the Exception for clearer error reportingif (t instanceof InvocationTargetException &&t.getCause() != null) {t = t.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}

老规矩不在本篇文章分析范围中的用省略号带过,可以看到分析一调用了Bootstrap的daemon.start()。接下来看代码块一 start

代码块一 start

  public void start() throws Exception {if (catalinaDaemon == null) {init();}Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);method.invoke(catalinaDaemon, (Object [])null);}

使用反射调用了org.apache.catalina.startup.Catalina的start方法

public void start() {if (getServer() == null) {load();}if (getServer() == null) {log.fatal("Cannot start server. Server instance is not configured.");return;}long t1 = System.nanoTime();// Start the new servertry {//分析二getServer().start();} catch (LifecycleException e) {log.fatal(sm.getString("catalina.serverStartFail"), e);try {getServer().destroy();} catch (LifecycleException e1) {log.debug("destroy() failed for failed Server ", e1);}return;}long t2 = System.nanoTime();if(log.isInfoEnabled()) {log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");}if (await) {await();stop();}}

看分析二,接着调用了getServer().start()。这个方法使用了模板设计模式,先是调用了

LifecycleBase抽象类的start方法,然后调用startInternal这个抽象方法,具体实现的方法在org.apache.catalina.core.StandardServer。这个是怎么来得,看上一篇文章代码块二的解析一。

接着我们看一下StandardServer的startInternal方法。看代码块二  startInternal

代码块二  StandardServer.startInternal()

 @Overrideprotected void startInternal() throws LifecycleException {fireLifecycleEvent(CONFIGURE_START_EVENT, null);setState(LifecycleState.STARTING);globalNamingResources.start();// Start our defined Servicessynchronized (servicesLock) {for (Service service : services) {// 分析三 启动standardService的startInternal方法service.start();}}}

接着往下看分析三

    protected void startInternal() throws LifecycleException {if(log.isInfoEnabled()) {log.info(sm.getString("standardService.start.name", this.name));}setState(LifecycleState.STARTING);// Start our defined Container first//启动容器if (engine != null) {synchronized (engine) {engine.start();}}//和jmx相关的线程池启动先不分析synchronized (executors) {for (Executor executor: executors) {executor.start();}}mapperListener.start();// Start our defined Connectors secondsynchronized (connectorsLock) {for (Connector connector: connectors) {try {// If it has already failed, don't try and start itif (connector.getState() != LifecycleState.FAILED) {//分析四,重点分析,connector和我们的请求相关的connector.start();}} catch (Exception e) {log.error(sm.getString("standardService.connector.startFailed",connector), e);}}}}

我们重点查看分析四,connector.start()这个是和我们的请求相关的类。以下是start中调用了connector.startInternal 看代码块三 startInternal

代码块三 connector.startInternal()

protected void startInternal() throws LifecycleException {// Validate settings before startingif (getPort() < 0) {throw new LifecycleException(sm.getString("coyoteConnector.invalidPort", Integer.valueOf(getPort())));}setState(LifecycleState.STARTING);try {//请求处理器 接着调用org.apache.coyote.AbstractProtocol的start方法protocolHandler.start();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);}}//org.apache.coyote.AbstractProtocol @Overridepublic void start() throws Exception {if (getLog().isInfoEnabled()) {getLog().info(sm.getString("abstractProtocolHandler.start", getName()));}//分析五 使用endpoint来处理socket请求endpoint.start();// Start timeout threadasyncTimeout = new AsyncTimeout();Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");int priority = endpoint.getThreadPriority();if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {priority = Thread.NORM_PRIORITY;}timeoutThread.setPriority(priority);timeoutThread.setDaemon(true);timeoutThread.start();}

我们接着往下看分析五

   @Overridepublic void startInternal() throws Exception {if (!running) {running = true;paused = false;processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());// Create worker collection//创建了具体进行处理http请求的线程池//一般情况下,我们自己不配置线程池,所以会进入这个方法,也可以自己在server.xml中配置这个线程池//创建一个核心线程数是10,最大线程数是200,队列长度是Integer.MaxValue的线程池//注意下,这边线程池的逻辑和JDK中线程池的逻辑不一样,默认创建10个线程,当请求数//超过10个的话会继续创建,最大创建200个线程,超过200个后,任务就会进入阻塞队列//值得注意的是Tomcat的线程池继承了JDK的ThreadPoolExecutor,但是重写了线程池的默认//机制。Tomcat的线程池会默认创建corePoolSize个线程,此时线程池中的线程都是空闲的。//随着不断向线程池中添加任务,空闲线程逐渐减少,当线程池中的空闲线程耗尽之前,任务//都会直接被提交到线程池的队列中(这些任务会立即被空闲线程消费),当线程池中没有空闲//线程而且线程池中的线程总数没达到MaximumPoolSize,会创建一个新的线程来执行新的任务;//当线程池的大小达到MaximumPoolSize时,直接将任务放进队列,等到有线程空闲下来后再处理//这个任务。(参考TaskQueue的offer方法)if (getExecutor() == null) {createExecutor();}initializeConnectionLatch();// Start poller threads//重点,这里创建了两个轮训器的守护线程,这两个线程一直轮训事件列表,如果有请求进来,就将请求分发给线程池处理。pollers = new Poller[getPollerThreadCount()];for (int i=0; i<pollers.length; i++) {pollers[i] = new Poller();Thread pollerThread = new Thread(pollers[i], getName() + "-我是监听请求事件的线程《2》-"+i);//  Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();}// 分析六 重点,这里就是我们的主线程再创建一个子线程,//这个子线程通过socket来获取客户端的请求,然后将该请求放入到一个请求事件列表中。//上面创建的poller线程就轮训这个事件列表,最后交给线程池处理,startAcceptorThreads();}}

我们可以看到这个方法,创建了一个线程池这个线程池是用于处理我们poller线程发送给他的连接请求,创建一个核心线程数是10,最大线程数是200,队列长度是Integer.MaxValue的线程池 //注意下,这边线程池的逻辑和JDK中线程池的逻辑不一样,默认创建10个线程,当请求数 //超过10个的话会继续创建,最大创建200个线程,超过200个后,任务就会进入阻塞队列。

然后还创建了多个poller线程,这个线程的数量一般是由我们的cpu核数来决定的大于两核就是2否则就是1。poller线程的run方法看下面的代码块六 poller.run

我们重点看分析六的代码块四 startAcceptorThreads

代码块四 startAcceptorThreads

  protected final void startAcceptorThreads() {int count = getAcceptorThreadCount();acceptors = new Acceptor[count];for (int i = 0; i < count; i++) {acceptors[i] = createAcceptor();String threadName = getName() + "-我是接收socket请求的线程《1》-" + i;//  String threadName = getName() + "-Acceptor-" + i;acceptors[i].setThreadName(threadName);Thread t = new Thread(acceptors[i], threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();}}

这里默认是创建了一个acceptor线程,为什么一个线程却是可以抗住高并发呢?我们接着往acceptor这个线程的run方法查看。代码块五 acceptor.run

代码块五 acceptor.run()

  //后台线程监听传入的TCP/IP连接并将其传递给适当的处理器。@Overridepublic void run() {//Thread.currentThread().setName("我是响应用户网页请求的线程");int errorDelay = 0;// Loop until we receive a shutdown commandwhile (running) {// Loop if endpoint is pausedwhile (paused && running) {state = AcceptorState.PAUSED;try {Thread.sleep(50);} catch (InterruptedException e) {// Ignore}}if (!running) {break;}state = AcceptorState.RUNNING;try {//if we have reached max connections, waitcountUpOrAwaitConnection();SocketChannel socket = null;try {// Accept the next incoming connection from the server// socket//分析一 通过nio的方式获取客户端的请求socket = serverSock.accept();} catch (IOException ioe) {// We didn't get a socketcountDownConnection();if (running) {// Introduce delay if necessaryerrorDelay = handleExceptionWithDelay(errorDelay);// re-throwthrow ioe;} else {break;}}// Successful accept, reset the error delayerrorDelay = 0;// Configure the socketif (running && !paused) {// setSocketOptions() will hand the socket off to// an appropriate processor if successful//分析二 处理这个请求if (!setSocketOptions(socket)) {closeSocket(socket);}} else {closeSocket(socket);}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error(sm.getString("endpoint.accept.fail"), t);}}state = AcceptorState.ENDED;}

接着看分析二

 protected boolean setSocketOptions(SocketChannel socket) {// Process the connectiontry {//disable blocking, APR style, we are gonna be polling itsocket.configureBlocking(false);Socket sock = socket.socket();socketProperties.setProperties(sock);NioChannel channel = nioChannels.pop();if (channel == null) {SocketBufferHandler bufhandler = new SocketBufferHandler(socketProperties.getAppReadBufSize(),socketProperties.getAppWriteBufSize(),socketProperties.getDirectBuffer());if (isSSLEnabled()) {channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);} else {channel = new NioChannel(socket, bufhandler);}} else {channel.setIOChannel(socket);channel.reset();}// 分析一 将请求的socket注册进我们刚刚创建的pollergetPoller0().register(channel);} catch (Throwable t) {ExceptionUtils.handleThrowable(t);try {log.error("",t);} catch (Throwable tt) {ExceptionUtils.handleThrowable(tt);}// Tell to close the socketreturn false;}return true;}

我们接着往下看

 public void register(final NioChannel socket) {socket.setPoller(this);NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);socket.setSocketWrapper(ka);ka.setPoller(this);ka.setReadTimeout(getSocketProperties().getSoTimeout());ka.setWriteTimeout(getSocketProperties().getSoTimeout());ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());ka.setReadTimeout(getConnectionTimeout());ka.setWriteTimeout(getConnectionTimeout());PollerEvent r = eventCache.pop();ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.if ( r==null) {r = new PollerEvent(socket,ka,OP_REGISTER);} else {r.reset(socket,ka,OP_REGISTER);}//这里就是添加一个事件,说有一个http请求进来了,那这个时候poller中循环的线程监控到说有事件进来了,然后开始处理。addEvent(r);}private final SynchronizedQueue<PollerEvent> events =new SynchronizedQueue<>();// 将我们的一个事件添加进入一个同步队列去了。private void addEvent(PollerEvent event) {events.offer(event);if (wakeupCounter.incrementAndGet() == 0) {selector.wakeup();}}

到现在我们终于知道为什么一个 acceptor线程就可以抗住高并发,那是因为它把这个请求直接给放入一个队列当中去了。由下游的poller去处理。

代码块六 poller.run

 public void run() {try {Poller poller = new Poller();} catch (IOException e) {e.printStackTrace();}// Loop until destroy() is calledwhile (true) {boolean hasEvents = false;try {if (!close) {hasEvents = events();if (wakeupCounter.getAndSet(-1) > 0) {// If we are here, means we have other stuff to do// Do a non blocking selectkeyCount = selector.selectNow();} else {keyCount = selector.select(selectorTimeout);}wakeupCounter.set(0);}if (close) {events();timeout(0, false);try {selector.close();} catch (IOException ioe) {log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);}break;}} catch (Throwable x) {ExceptionUtils.handleThrowable(x);log.error("",x);continue;}// Either we timed out or we woke up, process events firstif (keyCount == 0) {hasEvents = (hasEvents | events());}Iterator<SelectionKey> iterator =keyCount > 0 ? selector.selectedKeys().iterator() : null;// Walk through the collection of ready keys and dispatch// any active event.while (iterator != null && iterator.hasNext()) {SelectionKey sk = iterator.next();iterator.remove();NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();// Attachment may be null if another thread has called// cancelledKey()if (socketWrapper != null) {//分析一 轮训到我们的事件列表里面事件进来,可以理解为有请求过来了,现在去处理processKey(sk, socketWrapper);}}// Process timeoutstimeout(keyCount,hasEvents);}getStopLatch().countDown();}

这个run方法大概的含义就是一直循环遍历事件列表,如果有事件进来,那么我们就去处理这个请求。我们再看分析一

  protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {....if (sk.isReadable()) {if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {closeSocket = true;}}if (!closeSocket && sk.isWritable()) {//往下看processSocketif (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {closeSocket = true;}}
.......}public boolean processSocket(SocketWrapperBase<S> socketWrapper,SocketEvent event, boolean dispatch) {try {if (socketWrapper == null) {return false;}SocketProcessorBase<S> sc = processorCache.pop();if (sc == null) {sc = createSocketProcessor(socketWrapper, event);} else {sc.reset(socketWrapper, event);}Executor executor = getExecutor();//可以看到这里使用了线程池在实际处理我们的请求if (dispatch && executor != null) {executor.execute(sc);} else {sc.run();}} catch (RejectedExecutionException ree) {getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);return false;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);// This means we got an OOM or similar creating a thread, or that// the pool and its queue are fullgetLog().error(sm.getString("endpoint.process.fail"), t);return false;}return true;}

可以看到我们最后的请求到了线程池来处理这个请求,线程池处理的后续流程下一篇文章再做分析

总体流程图

总结

tomcat的start启动过程,一步一步的启动,主线程创建accpetor线程,accpetor线程阻塞的监听8080端口的请求进来,如果有请求进来,然后就把它放入一个事件列表中,又继续监听8080端口。主线程创建的poller线程就去轮训事件列表,如果有事件进来,那就交给线程池去处理。后续本篇文章会一步一步完善细节。下面放一张整个流程图

tomcat启动过程-start启动相关推荐

  1. ARM 之八 Cortex-M/R 内核启动过程 / 程序启动流程(基于IAR)

      在前面的文章<ARM 之 Cortex-M/R 内核启动过程 / 程序启动流程(基于ARMCC)>中已经介绍过了 Cortex-M/R 内核相关内容.这里基于 IAR 的启动流程与之前 ...

  2. WINCE6.0+S3C2443的启动过程---内核启动

    ********************************LoongEmbedded******************************** 作者:LoongEmbedded(kandi ...

  3. ARM 之九 Cortex-M/R 内核启动过程 / 程序启动流程(基于ARMCC、Keil)

    内核规范   ARM Cortex-M/R 内核的复位启动过程也被称为复位序列(Reset sequence).ARM Cortex-M/R内核的复位启动过程与其他大部分CPU不同,也与之前的ARM架 ...

  4. 简述windows计算机启动过程,计算机启动过程

    计算机启动过程 互联网   发布时间:2009-04-21 02:32:12   作者:佚名   我要评论 对于电脑用户来说,打开电源启动电脑几乎是每天必做的事情,但计算机在显示这些启动画面的时候在做 ...

  5. android uboot启动过程,Android启动流程简析(一)

    最近一时兴起,想对Android的启动流程进行一次分析,经过一番整理,从以下几个方面进行总结,代码部分只讨论思路,不论细节. Android架构介绍 Android启动概述 BootLoader介绍 ...

  6. linux内核启动过程和启动第一个应用程序

    内核的最终目的就是运行应用程序(位于根文件系统). 内核启动相关的一些重要过程 1.判断是否支持这个processor,即CPU. 2.判断是否支持U-boot传进来的机器ID. 机器ID是在在r1寄 ...

  7. 红帽linux的启动过程,Redhat启动telnet服务的过程

    1.检查是否安装telnet-server软件.rpm -q telnet-server. 2.在redhat光盘中找到安装文件:telnet-server-0.17-25.i386.rpm,命令为: ...

  8. optee的启动过程

    在ATF中opteed_setup()跳转到optee起始地址 optee起始代码位置 从kern.ld.S可以看到,无论是arm32还是arm64,代码段都是从_start函数处开始 OUTPUT_ ...

  9. Centos 启动过程详解

    Centos 启动过程 linux启动时我们会看到许多启动信息. Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 第一步:内核的引导 第二步:运行 init 第三步:系统 ...

最新文章

  1. java call by value_JAVA值传递(call by value)
  2. 英特尔分拆McAfee:31亿美元将多数股权卖给投资公司TPG
  3. linux 启动rabbitmq 报错:
  4. linux树莓派连接wifi密码,树莓派连接WiFi,不使用界面,多WiFi切换
  5. WF 创建 SQL 持久性数据库
  6. 大中台模式下如何构建复杂业务核心状态机组件
  7. E人E本的android突破与行业走向
  8. sql-查询不同状态下的数据之和
  9. 车道识别与交通标志识别
  10. PPT出图修改dpi
  11. uni-app框架简介
  12. 平台型组织——数字化时代的组织智商鉴定器
  13. Zynga公布2020年第三季度财务业绩
  14. 小爬虫。爬取网站多页的通知标题并存取在txt文档里。
  15. 奋斗的青春,无悔的时光
  16. 【调剂】2020年西安建筑科技大学考研调剂信息(含接收专业)
  17. mysql建表 括号_MySQL建表的问题,关于索引
  18. 转载--文章(感谢陈晨博主分享) 关于 Json.net
  19. 微软发布视频消息应用Qik:42秒录制、两周后自动消失
  20. php文字链接下划线怎么取消,html超链接怎么去掉下划线

热门文章

  1. A500的dial过程选择phone
  2. java 杨辉三角(贾宪三角,帕斯卡三角)
  3. linux系统使用实验报告操作系统,linux操作系统实验报告1.doc
  4. PS(PhotoShop)替换纯色图片的颜色
  5. 关于电力系统保护FC跳闸回路闭锁保护的分析
  6. Centos服务器上安装Tomcat
  7. java 的访问修饰符是什么?
  8. M-K趋势检验以及突变检验
  9. 新路程------imx6 wtd摘要
  10. Windows下的远程命令行工具pstools