在大型的Web应用中,Servlet容器可能同时会接收到多个客户端的上千个请求,如何快速地服务每个请求将显得十分重要。如果采用串行化的方式,一个请求一个请求地处理,将导致响应时间让人无法接受。较好的解决方案是采用多线程并发处理每个请求。在本章中,我们将介绍Servlet容器如何同时处理多个请求,以及如何开发线程安全的Servlet。
7.1 多线程的 Servlet 模型
Servlet 规范定义,在默认情况下(Servlet 不是分布式的环境中部署),Servlet容器对声明的每一个Servlet, 只创建一个实例。如果有多个客户请求同时访问这个Servlet,Servlet容器如何处理这多个请求呢?
答案是采用多线程,Servlet 容器 维护了一个线程池来服务器请求。线程池实际上是等待执行代码的一组线程,这些线程叫做工作线程(Worker Thread)。Servlet容器 使用一个调度者(Dispatcher Thread)来管理工作者线程。 当容器接收到一个访问Servlet的请求,调度者线程从线程池中选取一个工作线程,将请求传递给该线程,然后由这个线程执行Servlet 的 service()方法,如图7-1所示。
当这个线程正在执行的时候,容器收到了另外一个请求,调度者线程将从池中选取一个线程来服务新的请求。要注意的是,Servlet 容器并不关心这第二个请求时访问同一个 Servlet 还是另一个Servlet。 因此,如果容器同时收到访问同一个Servlet的多个请求,那么这个 Servlet 的service()方法将在多个线程中并发地执行。图7-2显示了两个工作者线程都在执行同一个Servlet的 service()方法。
由于Servlet 容器采用了单实例多线程的方式(这个是Servlet容器默认的行为),最大限度地减少了产生Servlet 实例的开销,显著地提升了对请求的响应时间。对于Tomcat,可以在server.xml 文件中通过
7.2 线程安全的 Servlet
Servlet 容器 采用多线程 提高了性能,但同时也对Servlet 的开发者提出了更高的要求,在开发Servlet时, 要注意线程安全的问题。我们以下两个方面来看看开发Servlet时,要注意的线程安全的问题。
7.2.1 变量 的线程安全
我们先看一下 7-1 .
7.2.2 属性 的线程安全
7.3 SingleThreadModel 接口
javax.servlet.SingleThreadModel 接口 没有任何的方法,它是一个标识接口。如果一个Servlet 实现了这个接口,Servlet 容器 将保证一个时刻仅有一个线程可以在给定的servlet实例的 service()方法中执行。如果所有的客户请求,都交由一个Servlet进行序列化处理,那么将严重地降低性能。为了避免性能问题,Servlet容器会创建多个Servlet实例,并将这些实例放到实例池中,对于每个客户请求,容器从池中分配Servlet实例,并在完成服务后,由容器将实例放回池中。
很多开发者认为只要Servlet实现了SingleThreadModel 接口,就能解决线程安全的问题,实际上,这是一种误解。使用SingleThreadModel接口,并不能真正解决并发访问的问题,反而让人忽视了线程安全的问题。虽然,Servlet容器保证了在一个时刻仅有一个线程可以在一个Servlet实例的service()方法中执行,但是Servlet 容器会创建多个实例,为同时到来的请求服务,而这多个实例如果访问了共享的数据(例如,HttpSession 和 ServletContext 的属性,以及外部的文件),就将出现线程安全的问题。 SingleThreadModel 接口在Servlet2.4规范中就已经废弃了(不赞成使用)。