理解 Servlet 的单实例多线程
您目前处于:技术核心竞争力  2016-08-12

一. Servlet 容器

1. Java 的内存模型 JMM(Java Memory Model)

JMM 主要是为了规定了线程和内存之间的一些关系。根据 JMM 的设计,系统存在一个主内存 (Main Memory),Java 中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存 (Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。

根据 JMM,我们可以将 Servlet 实例的内存模型抽象为图所示的模型。 

Servlet 采用多线程来处理多个请求同时访问。Servlet 依赖于一个线程池来服务请求。线程池实际上是一系列的工作者线程集合。Servlet 使用一个调度线程来管理工作者线程。 

当容器收到一个 Servlet 请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行 Servlet 的 service 方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个 Servlet。

当容器同时收到对同一个 Servlet 的多个请求的时候,那么这个 Servlet 的 service() 方法将在多线程中并发执行。 

Servlet 容器默认采用单实例多线程的方式来处理请求,这样减少产生 Servlet 实例的开销,提升了对请求的响应时间,对于 Tomcat 可以在 server.xml 中通过 Connector 元素设置线程池中线程的数目。 

2. Servlet 容器

从上图可以看出 Tomcat 的容器分为四个等级,真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程。

二、Servlet

Servlet 是在 javax.servlet 包中定义的一个接口。它声明了 Servlet 生命周期中必不可少的三个方法 - init()、service() 和 destroy()。每个 Servlet 都必须实现这三个方法,而且由服务器在特定的时刻调用。

1. init(ServletConfig) 方法:负责初始化 Servlet 对象,在 Servlet 的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题;

当 Server Thread 线程执行 Servle 实例的 init() 方法时,所有的 Client Service Thread 线程都不能执行该实例的 service() 方法,更没有线程能够执行该实例的 destroy() 方法,因此 Servlet 的 init() 方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。

2. service(ServletRequest req,ServletResponse res) 方法:负责响应客户的请求;为了提高效率,Servlet 规范要求一个 Servlet 实例必须能够同时服务于多个客户端请求,即 service() 方法运行在多线程的环境下,Servlet 开发者必须保证该方法的线程安全性;

当服务器接收到来自客户端的多个请求时,服务器会在单独的 Client Service Thread 线程中执行 Servlet 实例的 service() 方法服务于每个客户端。此时会有多个线程同时执行同一个 Servlet 实例的 service() 方法,因此必须考虑线程安全的问题。

3. destroy() 方法:当 Servlet 对象退出生命周期时,负责释放占用的资源。


Servlet的生命周期:

Servlet 类加载 — 实例化 — 服务 — 销毁


Servlet 容器和 Web 服务器如何处理请求?

1. Web 服务器接收 HTTP 请求。

2. Web 服务器将请求转发给 Servlet 容器。

3. 如果容器中不存在所需的 Servlet,容器就会检索 Servlet,并将其加载到容器的地址中间中。

4. 容器调用 Servlet 的 init 方法对 Servlet 进行初始化(该方法只会在 Servlet 第一次被载入时被调用)。

5. 容器调用 Servlet 的 service 方法来处理 HTTP 请求,即读取请求中的数据,创建一个响应。Servlet 会被保留在容器的地址空间中,继续处理其他的 HTTP 请求。

6. Web 服务器将动态生成的结果返回到正确的地址。


Servlet 线程安全

如果 service() 方法没有访问 Servlet 的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request 和 response 对象等。该方法本身就是线程安全的,不必进行任何的同步控制。

如果 service() 方法访问了 Servlet 的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。

如果 service() 方法访问了 Servlet 的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。

如果 service() 方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。

如果 service() 方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。



转载请并标注: “本文转载自 linkedkeeper.com ”  ©著作权归作者所有