扫码关注公众号:芋道源码

发送: 百事可乐
获取永久解锁本站全部文章的链接

《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 https://juejin.im/post/5a6d73a36fb9a01cba42d1d7 「预流」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

在默认的配置下Tomcat启动好之后会看到后台上总共有6个线程在运行。其中1个用户线程,剩下5个为守护线程(如下图所示)。

img

如果你对用户线程、守护线程等概念不熟悉,请参看前一篇文章——

Tomcat 7 服务器关闭原理

。 这里重点关注以 http-bio-8080 开头的两个守护线程(即 http-bio-8080-Acceptor-0 和 http-bio-8080-AsyncTimeout ),因为这是我们在 Tomcat 的默认配置下发布 web 应用时实际处理请求的线程。先看下这两个线程在容器启动时是如何产生和启动的。

在前面将 Tomcat 启动的系列文章中看到 Tomcat 容器启动时会用 Digester 读取 server.xml 文件产生相应的组件对象并采取链式调用的方式调用它们的 init 和 start 方法,在 Digester 读取到 server.xml 中的 connector 节点时是这么处理的:

digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");

以上代码见org.apache.catalina.startup.Catalina类的 366 到 372 行。所以在碰到 server.xml 文件中的 Server/Service/Connector 节点时将会触发 ConnectorCreateRule 类的 begin 方法的调用:

 1	    public void begin(String namespace, String name, Attributes attributes)
2 throws Exception {
3 Service svc = (Service)digester.peek();
4 Executor ex = null;
5 if ( attributes.getValue("executor")!=null ) {
6 ex = svc.getExecutor(attributes.getValue("executor"));
7 }
8 Connector con = new Connector(attributes.getValue("protocol"));
9 if ( ex != null ) _setExecutor(con,ex);
10
11 digester.push(con);
12 }

在第 8 行,会根据配置文件中 Server/Service/Connector 节点的 protocol 属性调用 org.apache.catalina.connector.Connector 类的构造方法,而默认情况下 server.xml 文件中 Server/Service/Connector 节点共有两处配置:

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

先看第一个 Connector 节点,调用 Connector 的构造方法时会传入字符串 HTTP/1.1

 1	    public Connector(String protocol) {
2 setProtocol(protocol);
3 // Instantiate protocol handler
4 try {
5 Class<?> clazz = Class.forName(protocolHandlerClassName);
6 this.protocolHandler = (ProtocolHandler) clazz.newInstance();
7 } catch (Exception e) {
8 log.error(sm.getString(
9 "coyoteConnector.protocolHandlerInstantiationFailed"), e);
10 }
11 }

这里先会执行 org.apache.catalina.connector.Connector 类的 setProtocol 方法:

 1	    public void setProtocol(String protocol) {
2
3 if (AprLifecycleListener.isAprAvailable()) {
4 if ("HTTP/1.1".equals(protocol)) {
5 setProtocolHandlerClassName
6 ("org.apache.coyote.http11.Http11AprProtocol");
7 } else if ("AJP/1.3".equals(protocol)) {
8 setProtocolHandlerClassName
9 ("org.apache.coyote.ajp.AjpAprProtocol");
10 } else if (protocol != null) {
11 setProtocolHandlerClassName(protocol);
12 } else {
13 setProtocolHandlerClassName
14 ("org.apache.coyote.http11.Http11AprProtocol");
15 }
16 } else {
17 if ("HTTP/1.1".equals(protocol)) {
18 setProtocolHandlerClassName
19 ("org.apache.coyote.http11.Http11Protocol");
20 } else if ("AJP/1.3".equals(protocol)) {
21 setProtocolHandlerClassName
22 ("org.apache.coyote.ajp.AjpProtocol");
23 } else if (protocol != null) {
24 setProtocolHandlerClassName(protocol);
25 }
26 }
27
28 }

所以此时会调用setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol")从而将 Connector 类实例变量 protocolHandlerClassName 值设置为org.apache.coyote.http11.Http11Protocol,接下来在 Connector 的构造方法中就会根据 protocolHandlerClassName 变量的值产生一个org.apache.coyote.http11.Http11Protocol对象,并将该对象赋值给 Connector 类的实例变量 protocolHandler 。在 Http11Protocol 类的构造方法中会产生一个org.apache.tomcat.util.net.JIoEndpoint对象:

1	    public Http11Protocol() {
2 endpoint = new JIoEndpoint();
3 cHandler = new Http11ConnectionHandler(this);
4 ((JIoEndpoint) endpoint).setHandler(cHandler);
5 setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
6 setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
7 setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
8 }

几个相关对象的构造方法调用时序图如下所示,其中org.apache.coyote.AbstractProtocolorg.apache.coyote.http11.Http11Protocol的父类org.apache.tomcat.util.net.AbstractEndpointorg.apache.tomcat.util.net.JIoEndpoint的父类。

img

接下来容器启动各组件时会调用

org.apache.catalina.connector.Connector

的 start 方法,如前面分析 Tomcat 启动时所述,此时会调用

org.apache.catalina.connector.Connector

类的 startInternal 方法:

protected void startInternal() throws LifecycleException {

// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}

setState(LifecycleState.STARTING);

try {
protocolHandler.start();
} catch (Exception e) {
String errPrefix = "";
if(this.service != null) {
errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
}

throw new LifecycleException
(errPrefix + " " + sm.getString
("coyoteConnector.protocolHandlerStartFailed"), e);
}

mapperListener.start();
}

在第 12 行,将会调用实例变量 protocolHandler 的 start 方法。在上面分析 Connector 类的构造函数时发现 protocolHandler 变量的值就是org.apache.coyote.http11.Http11Protocol对象,所以此时将会调用该类的 start 方法。在 Http11Protocol 类中没有定义 start 方法,这里将会调用其父类org.apache.coyote.AbstractProtocol中的 start 方法:

public void start() throws Exception {
if (getLog().isInfoEnabled())
getLog().info(sm.getString("abstractProtocolHandler.start",
getName()));
try {
endpoint.start();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.startError",
getName()), ex);
throw ex;
}
}

这里会调用 endpoint 对象的 start 方法,而 endpoint 是org.apache.tomcat.util.net.JIoEndpoint类的实例(在上面讲 Http11Protocol 类的构造方法时所提到),这里最终会执行该类的 startInternal 方法:

 1	    @Override
2 public void startInternal() throws Exception {
3
4 if (!running) {
5 running = true;
6 paused = false;
7
8 // Create worker collection
9 if (getExecutor() == null) {
10 createExecutor();
11 }
12
13 initializeConnectionLatch();
14
15 startAcceptorThreads();
16
17 // Start async timeout thread
18 Thread timeoutThread = new Thread(new AsyncTimeout(),
19 getName() + "-AsyncTimeout");
20 timeoutThread.setPriority(threadPriority);
21 timeoutThread.setDaemon(true);
22 timeoutThread.start();
23 }
24 }

正是在这里产生并启动本文开头提到的 http-bio-8080-Acceptor-0 和 http-bio-8080-AsyncTimeout 两个线程。第 17 到 22 行就是产生和启动 http-bio-8080-AsyncTimeout 线程,第 15 行这里调用父类org.apache.tomcat.util.net.AbstractEndpoint的 startAcceptorThreads 方法:

 1	    protected final void startAcceptorThreads() {
2 int count = getAcceptorThreadCount();
3 acceptors = new Acceptor[count];
4
5 for (int i = 0; i < count; i++) {
6 acceptors[i] = createAcceptor();
7 String threadName = getName() + "-Acceptor-" + i;
8 acceptors[i].setThreadName(threadName);
9 Thread t = new Thread(acceptors[i], threadName);
10 t.setPriority(getAcceptorThreadPriority());
11 t.setDaemon(getDaemon());
12 t.start();
13 }
14 }
15
16
17 /**
18 * Hook to allow Endpoints to provide a specific Acceptor implementation.
19 */
20 protected abstract Acceptor createAcceptor();

在这里将产生和启动 http-bio-8080-Acceptor-0 线程。注意在构造该线程时第 6 行将会调用第 20 行的抽象方法,该方法的具体实现是在 JIoEndpoint 类中:

@Override
protected AbstractEndpoint.Acceptor createAcceptor() {
return new Acceptor();
}

以上便是本文开头所述的两个后台线程产生和启动的流程,其相关类调用的时序图如下图所示:

img

同理,ajp-bio-8009-Acceptor-0 和 ajp-bio-8009-AsyncTimeout 两个守护线程的产生和启动方式也是一致的,不再赘述。

文章目录