为什么EJB是线程安全的,而Servlet不是?

10

据我的理解,Servlet容器创建有限数量的servlet实例并为每个servlet实例创建多个线程以便重复使用这些线程和实例。

由于线程存在多个实例,它们不是“线程安全”的(尽管我知道编写线程安全代码并不困难)。

另一方面,EJB容器不会创建EJB的线程,而是仅重复使用EJB对象(使用池)。由于没有EJB实例的多个线程,因此不存在线程安全问题。

我的问题是:为什么行为不同?将EJB作为Servlet(线程不安全)是否不是一个好主意?

我相信我缺少了什么,并想了解那个缺失之处。


为什么它们应该保持一致呢?它们执行完全不同的任务。 - user207421
@EJP,没错,它们解决不同的目的,但在无状态方面有相似之处。由于这种相似性,EJB可以通过具有线程而不仅仅是实例来提高性能效率。 - Sandeep Jindal
如果您对于一个Servlet/一个实例的事情感兴趣,可以查看这篇文章:http://piotrnowicki.com/2012/04/one-servlet-instance-to-rule-them-all/ - Piotr Nowicki
它们在实现方式、容器构建的实例数量、各自规范等方面也存在差异。所有这些基本上决定了你所询问的'一致性'的缺乏。根本没有理由期望如此不同的事物之间存在任何行为上的一致性。 - user207421
3个回答

10
回答如下:

回答您的问题,当然可以将EJBs像Servlets一样工作,EJB 3.1中增加了一个组件可以实现这一点:@Singleton

@Singleton bean可以像Servlet一样支持多线程,方法如下:

  • 使用@ConcurrencyManagement(BEAN)
  • 在需要并发的方法上使用@ConcurrencyManagement(CONTAINER)以及@Lock(READ),对于不支持线程安全的方法使用@Lock(WRITE)

Servlet有一个多年支持的功能,即<load-on-startup>允许Servlet在应用程序启动时进行负载和工作。

为了匹配Servlet的<load-on-start>,我们添加了@Startup注释,它可以添加到任何@Singleton EJB中,并在应用程序启动时启动。这些bean将在应用程序启动时调用其@PostConstruct方法,并在应用程序关闭时调用其@PreDestroy方法。

与其使用数字(<load-on-startup>1</load-on-startup>)来指定注释为@Startup的bean启动顺序,您可以使用@DependsOn注释并指定需要在注释bean之前启动的一组bean。

在EJB 3.1中,我们为了使Servlet和EJB对齐,一个较少知道和理解的方面是允许将EJB打包在.war文件中 - 这不是较少知道的部分 - 在这样做时,我们悄悄地更改了java:comp/env的定义以匹配Servlet方法。

在 EJB 3.1 之前,没有可能让两个 EJB 共享一个 java:comp/env 命名空间(根据 EJB 规范,java:comp/env 是 bean-scoped 的)。相比之下,Servlets 从来没有一种方法让单个 Servlet 拥有自己的私有的 java:comp/env 命名空间(根据 Servlet 规范,java:comp/env 是 module-scoped 的)。所以在 EJB 3.1 中,打包在 war 文件中的 EJB 将具有与 Web 应用程序中所有其他 Servlets 和 EJBs 相同的 module-scoped 的 java:comp/env 命名空间,这与打包在 EAR 文件中的 EJB 在其外部拥有 bean-scoped 的 java:comp/env 命名空间形成了很大的对比。我们就这个问题辩论了几个星期。
一个不错的闲聊话题,可以考验你的朋友们。

这对我来说是个好消息,因为JEE6中的EJB提供了多线程选项。这让我很高兴,因为我的理解是正确的 :) - Sandeep Jindal

9
也许是因为它们的设计目标不同。Servlet API是一个简单的API,非常接近HTTP协议,您可以在其上构建应用程序或框架。由于HTTP协议完全无状态,我想也许构建一个无状态的API是有意义的。构建在servlet API之上的几个框架(例如Stripes)每个请求使用一个Action实例,该实例不会同时使用。EJB是一个更复杂和高级的框架,旨在尽可能简单地实现事务业务逻辑。它更加重量级,并且具有有状态组件。这些组件显然需要是线程安全的。因此,使无状态bean也线程安全是很自然的选择。值得注意的是,默认情况下,Spring bean是单例的,因此必须遵循与servlet相同的规则。因此,可以提供更多或更少相同功能的多种设计。线程与性能优化无关。如果您需要同时处理3个请求,则无论请求是否发送到servlet或EJB,都需要3个线程。

2
Servlets和EJBs都可以开始/提交事务,并且自Java EE 6以来,两者都可以是多线程的。实际上,这只是EJB专家组比Servlet专家组更保守相同决定的问题,直到最近几年为止。在EJB 3.2中,我们将彻底摆脱那个虚假的“不能使用文件”的限制,这从来不应该超过建议,并且应该在EE规范而不是EJB规范中。 - David Blevins
感谢JB的详细解释。我明白Servlets通常比EJBs轻巧。但另一方面,无状态的EJBs(这是EJBs中最常见的类型)提供类似的功能,并且不应存储任何状态。与servlets类似,EJBs可以很容易地编写成“线程安全”的方式。由于这是可行的,创建线程比创建实例更有效率。对吗? - Sandeep Jindal
2
首先,无状态 bean 可以有状态。它们不能具有会话状态,这是非常不同的。其次,正如 David Blevins 在他的回答中所解释的那样,您可以使用单例,这类似于 servlets。第三,EJB 和 servlets 中使用线程来处理并发请求。唯一的区别在于,所有并发线程都使用单个 servlet 实例,而 EJB 一次仅由一个线程使用。为什么你认为创建 bean 实例很昂贵?例如,在Java中创建对象比执行SQL查询快多个数量级。 - JB Nizet
感谢@JBNizet。我有多个问题:1)无状态EJB的状态与servlet非常相似(是这样吗?)。2)我了解SQL的成本比创建EJB实例要高得多,但行业和应用程序容器尽可能地进行优化,因此servlet线程(是这样吗?)虽然会产生负面影响,但开发人员必须小心。 - Sandeep Jindal
1
线程与性能优化无关。如果您需要同时处理3个请求,则需要3个线程,无论请求是发送到servlet还是EJB。Servlet是单例(只创建一个实例),而EJB不是(创建多个实例)。但是创建实例的成本可能不是导致这两个API设计的原因。单例servlet非常方便许多情况,并且不会引起太多实现问题。单例EJB更具问题性,特别是有状态的EJB(无法作为单例实现),因此它们不是单例。 - JB Nizet
好的。这是一个很好的解释。你能再详细一些,提到单例servlet设计的原因吗?我认为“不创建对象,而是线程”的性能是主要且可能是唯一的原因。从你的解释中,似乎并不是这样。 - Sandeep Jindal

3
您最好的答案来自于javax.servlet.SingleThreadedModel接口的Javadoc文档:

已弃用。自Java Servlet API 2.4版本起不再使用。

public interface SingleThreadModel

确保servlet一次只处理一个请求,该接口没有方法。

如果servlet实现了此接口,则保证在servlet的服务方法中不会并发执行两个线程。Servlet容器可以通过同步访问单个servlet的实例或维护一个servlet实例池并将每个新请求分派到空闲的servlet来保证此功能。

请注意,SingleThreadModel 无法解决所有的线程安全问题。例如,会话属性和静态变量仍然可以被多个请求的多个线程同时访问,即使使用SingleThreadModel servlets也是如此。建议开发人员采用其他方式来解决这些问题,而不是实现此接口,例如避免使用实例变量或同步访问访问这些资源的代码块。此接口在Servlet API版本2.4中已过时。


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