Servlet的每个实例与每个线程之间的区别是什么?

85

Servlet类是否有多个实例?当我听到“每个servlet实例”时,有人可以详细解释一下吗?


不!Web容器只会在首次请求时通过调用newInstance()方法创建Servlet的一个实例。 - Sonoo Jaiswal
6个回答

189

当Servlet容器启动时,它会执行以下操作:

  1. 读取web.xml
  2. 在类路径中查找声明的 Servlet;并且
  3. 仅加载和实例化每个Servlet一次

大致上是这样的:

String urlPattern = parseWebXmlAndRetrieveServletUrlPattern();
String servletClass = parseWebXmlAndRetrieveServletClass();
HttpServlet servlet = (HttpServlet) Class.forName(servletClass).newInstance();
servlet.init();
servlets.put(urlPattern, servlet); // Similar to a map interface.

这些Servlet存储在内存中,并且每当请求的URL与Servlet的关联的url-pattern匹配时就会被重复使用。 然后,servlet容器执行类似以下代码的代码:

for (Entry<String, HttpServlet> entry : servlets.entrySet()) {
    String urlPattern = entry.getKey();
    HttpServlet servlet = entry.getValue();
    if (request.getRequestURL().matches(urlPattern)) {
        servlet.service(request, response);
        break;
    }
}

根据 HttpServletRequest#getMethod() 的不同,GenericServlet#service() 将决定调用哪个方法,如 doGet()doPost() 等等。

你看,servlet 容器为每个请求重复使用同一个 servlet 实例。换句话说:servlet 在每个请求之间共享。这就是为什么编写线程安全的 servlet 代码非常重要,实际上很简单:只需将请求或会话作用域数据分配为方法局部变量而不是作为 servlet 实例变量。例如:

public class MyServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

21
+1. 我只想补充一点,如果同一个Servlet类在web.xml中被映射到两个不同的URL上,则会创建两个实例。但是总的原则仍然适用,一个实例可以服务于多个请求。 - Yoni
1
变量thisIsNOTThreadSafe在类中是共享给不同用户还是仅共享给同一用户的不同页面。我的意思是,如果我在我的计算机上浏览此页面,而您在您的计算机上运行,则我们是否共享thisIsNOTThreadSafe的相同空间?谢谢。 - overshadow
1
是的,@overshadow,如果你的意思是两个客户端都访问同一个JVM,那么应该只有一个servlet,因此私有属性thisIsNOTThreadSafe将在会话之间共享。即使您注销并重新登录。 - Ricardo
1
你好,我的理解是这些Servlet实例存储在JVM中,当请求到来时,Servlet容器会在JVM中搜索这些实例。这正确吗? - T8Z
1
以上三个步骤是否都在容器启动时运行?这是否意味着Servlet在容器启动时初始化?那么为什么我们要使用<load-on-startup>? - Asif Mushtaq
显示剩余3条评论

32

不,servlet只有一个实例,可以为来自多个客户端的多个请求重复使用。这导致两个重要规则:

  • 在servlet中不要使用实例变量,除了应用程序范围内的值,最常从上下文参数中获取。
  • 在servlet中不要使方法同步synchronized

(相同的规则也适用于servlet过滤器和JSP)


那么,对于重复的代码,您是否建议将同步方法移动到外部类中的简单方法中? - Ommadawn

11
根据Java Servlet规范3.0版(第6-7页),每个JVM声明将有一个实例,除非Servlet实现了SingleThreadModel,在这种情况下,每个JVM可能会有多个实例。

Servlet规范3.0(第6-7页)2.2.1节关于Single Thread Model的说明中写道:“本规范版本中已弃用SingleThreadModel接口。” - Reva
@Reva 虽然该接口已被弃用,但它几乎肯定永远不会被删除,容器将无限期支持它。一旦成为规范的一部分,就永远是规范的一部分。 - Christopher Schultz

6
尽管已经有一些很好的答案,但它们都没有涉及部署在分布式环境中的Java Web应用程序。这是一个实际场景,实际上会创建多个单个Servlet的实例。在分布式环境中,您有一组机器来处理请求,请求可以发送到其中任何一台机器。每台机器都应该能够处理请求,因此每台机器都应该在其JVM中拥有MyAwesomeServlet的一个实例。
因此,正确的说法是每个Servlet每个JVM只有一个实例,除非它实现了SingleThreadModel。
简单地说,SingleThreadModel表示您必须每个Servlet实例只有一个线程,因此基本上需要为每个请求创建一个实例来处理它,这基本上破坏了以并行方式处理请求的整个概念,并且不被认为是一种良好的实践,因为Servlet对象的创建和初始化需要时间,直到它准备好处理请求。

如果你真的想要非常严谨(而且显然你确实想要),那么Servlet的唯一性不是基于每个JVM,而是基于每个上下文。你可以在一个或多个JVM中的多个上下文路径下部署相同的应用程序,并且它们每个都将拥有该Servlet的一个实例。 - Christopher Schultz
@ChristopherSchultz: 我没太理解,你能否请给我一些材料来更好地理解它?谢谢。 - Saurabh Patil
如果MyAwesomeServlet是应用程序WAR的一部分,并且您将其部署到两个上下文(例如/foo/bar),那么您将拥有两个MyAwesomeServlet类定义的副本,实际上,在同一时间内在内存中有两个MyAwesomeServlet实例。因此,每个JVM实际上可以有多个servlet实例。请注意,JVM认为这些是不同的类,因为它们由不同的ClassLoader加载。Servlet规范需要一个单独的ClassLoader,并且概念类=“ClassLoader+Class”通常适用于Java。 - Christopher Schultz

5

一个servlet类不能有多个实例。即使只有一个servlet实例,它也能处理多个请求。因此,最好不要使用类级别的变量。


5

对于那些了解真正的JavaScript(而不仅仅是它的库)的人来说,Servlets可以被视为函数对象。作为功能对象,它们的主要任务是做某些事情,而不是在它们的胸膛中存储一些信息。没有必要实例化每个这样的功能对象的多个实例,与Java类方法在该类的所有实例之间共享的原理相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接