为什么Servlet中的实例变量不是线程安全的?

8

当我阅读《Head First Servlet and JSP》时,它们说实例变量是非线程安全的。

我不太理解这个说法。例如:我有一个叫做ActionServlet.java的servlet。每次,每个用户的请求发送到服务器时,容器将创建一个新的线程并创建新的ActionServlet实例。

ActionServlet可能具有以下结构:

public class ActionServlet extends HttpServlet {
   // example of instance variable
   Instance variable;
   public void processRequest(HttpServletRequest request, HttpServletResponse response) {   
       // process something relating to instance variable
   }
}

因此,由于所有这些线程都为 ActionServlet 创建一个新的类实例,所以我在这里没有看到任何问题。因为这些线程的实例是相互分离的。
请找出在多线程环境中使用实例变量时的问题所在。
谢谢 :)

3
考虑在两个线程中对同一个对象调用相同的方法。 - Daniel Fischer
@DanielFischer 我无法想象不同的线程如何使用相同的对象,因为:1)这些变量是私有的2)该线程的对象始终与其他线程不同(我认为)。我在上面提供了一个示例代码。请告诉我更清楚一些。 - hqt
2
@hqt:容器创建一个唯一的servlet实例,将其存储在某个全局数据结构中(例如一个映射表),每当请求到来时,它从映射表中获取相应的servlet(基于请求路径),并调用其服务方法。servlet字段是私有的事实并没有任何影响。容器甚至不关心它们。 - JB Nizet
@JBNizet 哦,感谢您清晰的解释。您下面的评论对我仍然很有帮助:D 我还有一个最后的问题:那么,当没有任何线程使用该servlet实例时,该实例将被删除并在需要时重新创建,是吗? - hqt
3
Servlet并不存储在线程池中,线程存储在线程池中。Servlet在部署时或首次需要时创建,并保留在内存中直至应用程序卸载。如果它从内存中删除,容器将在新请求到来时重新创建实例,从而违反规范。无论如何也没有充分的理由将其从内存中删除,因为Servlet通常是无状态的,因此几乎不消耗任何内存。 - JB Nizet
你知道这对于在Spring中连接的服务/组件类是否成立吗? - IcedDante
3个回答

19
你所犯的错误在这里:

因此,由于所有这些线程都为ActionServlet创建一个新的类实例,所以我在这里没有看到任何问题。 因为这些线程的实例彼此分离。

容器不为每个请求创建Servlet类的新实例。 它重用现有实例。 这就是它们不是线程安全的原因。
Stripes Action Framework确实为每个请求创建新实例,因此在该框架下可以做出这样的假设。 但是,例如Struts 1遵循Servlet模型,不会为每个请求创建新的操作。
这并不意味着容器仅限于单个实例,理论上它可以创建多个实例,但这不是指定的行为,因此不能依赖它。 大多数流行的容器都不这样做。

4
实际上,容器必须只创建一个实例。规范如下所述:"对于未托管在分布式环境(默认情况)中的servlet,容器必须每个servlet声明仅使用一个实例"。 - JB Nizet
哦,感谢@JBNizet和Will Hartung。这本书没有向我展示容器只为一个servlet创建一个实例。 - hqt

3
因为所有这些线程都创建一个新的类实例(action.java),所以我不认为有任何问题。
您假设每个线程都会创建一个仅由该线程使用的类实例,这就是为什么您没有任何问题的原因。
但是请尝试想象一下,在您的特定示例中,如果两个线程访问相同的实例会发生什么。如果两个线程同时使用您的请求(request)和响应(response)成员,会发生什么? 也许您将从无法识别的请求中读取数据,并编写混合了两个部分的不一致响应。
因此,在您的情况下,实例变量也不是线程安全的,因为如果两个线程访问相同的实例,它们可能会干扰彼此。

0
问题在于,您的action.java并非总是实例化,而是从实例池中获取,请求线程也是从线程池中获取的,因此一个servlet实例可能会被多个请求共享。

4
Servlet 没有实例池。每个 Servlet 声明只有一个 Servlet 实例。 - JB Nizet

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