【问题标题】:EJB Pooling vs Thread-safe and @PreDestroyEJB 池与线程安全和@PreDestroy
【发布时间】:2015-11-20 17:19:28
【问题描述】:

我不明白 EJB 容器如何使用实例变量管理 @Stateless bean 的线程安全。所以在解释我的担忧之前,我先举一个简单的例子:

@Stateless
public class BeanTest{

@Inject
private String var;

private Connection connection;

@Resource(name = "jdbc/TestDB")
private DataSource dataSource;

public void modify() {
    var = "TestName";
}

@PostConstruct
public void initialize() {
    try {
        connection = dataSource.getConnection();
    } catch (SQLException sqle) {
        sqle.printStackTrace();
    }
}

@PreDestroy
public void cleanup() {
    try {
        connection.close();
        connection = null;
    } catch (SQLException sqle) {
        sqle.printStackTrace();
    }
}
}

这是我的问题,假设我们的容器支持池化:

1.池化 vs 线程安全:

用户 1 使用了 BeanTest 的实例,并使用 modify 方法修改了 var,然后他完成并且容器将 BeanTest 的实例放入托管池中。当用户 2 尝试对请求使用相同的 bean 时,他可能得到相同的 BeanTest 实例,最初由用户 1 修改(我知道这也可能得到另一个实例)。那么他会找到实例变量var 的哪个状态(默认值null"TestName")?如果它是新修改的,这是否意味着即使@Stateless bean 也不是 100% 线程安全的?所以最后没有关于线程安全的容器附加值,因为不使用实例变量使 bean 线程安全,即使它不是 @Stateless bean

2。池化 vs @PreDestroy

如果 bean 返回到托管池并且没有被销毁,这是否意味着不会调用 @Predestroy 方法并且在这种情况下连接将保持打开状态?因此,如果池中有 30 个该 bean,我们可能有 30 个未使用的打开连接,这不是性能问题吗?或者这不是@Predestroy 与池结合的方式? (使用Connection 只是一个例子,我们可能有其他类型的资源需要在@Predestroy 中关闭)

注意: 这不是一个真实的例子,所以我不是在寻找替代解决方案,我关心的是理解整个概念以及如何在应用程序中管理事物服务器,幕后发生了什么

【问题讨论】:

  • 您的担心正是我尽可能避开 EJB 的原因。使用 Spring,您不会得到这种无意义、定义不明确的语义。
  • 也许您应该使用@Stateful 而不是@Stateless
  • 另外,您应该在需要时获得连接,而不是在初始化 ejb 时。
  • @BheshGurung 感谢您的回复,但我想了解为什么它不是线程安全的,我不是在寻找替代方案,因为它不是现实生活中的问题
  • 实际上是线程安全的。为什么说它不是线程安全的?如果某个其他线程已经获得了实例,则该线程不可能获得它。

标签: java multithreading jakarta-ee ejb ejb-3.1


【解决方案1】:
  1. 如果注入的AnotherBean 是无状态的,那么调用AnotherBean.setName() 是没有意义的。如果它是有状态的,那么在无状态的 bean 中注入有状态 bean 是没有意义的(检查有状态 bean 的用途)。关于线程安全:容器不能使您的类本身线程安全,但它保证单个客户端线程一次将使用某个 EJB 实例(查看 Chris 回答中的引文)。因此,如果您不在 EJB 客户端的不同线程中使用 EJB,那么您的代码通过使用 thread confinment 是线程安全的。
  2. 不要在无状态 bean 中使用注入实例变量以外的实例变量。否则,您与 @Stateful 注释不一致。因此,删除 connection 状态变量。无论如何,您不想只为您的 bean 长时间保持打开的连接:每次都从数据源获取它。也不要忘记在使用后关闭连接(在 try/finally 子句中)。使用池化,它就像一个 JDBC 连接池:当池/EJB 容器决定它不再需要该实例时(例如,当它想要减少实例数量或抛出非 application exception 时),然后 @将调用 987654327@ 方法。

【讨论】:

  • 感谢 Andrei 的回复,我了解什么是有状态 bean,我同意您的第一点,但它并没有回答我关于线程安全的问题。因此,我将修改示例并使用一个简单的实例变量...请注意,这不是现实生活中的示例,而只是我给出的一个示例,以进一步澄清我的担忧
  • 对于第二个答案,连接也只是一个例子,我需要了解的是幕后发生了什么? @Predestroy 如何处理池化?
  • @Tarik 我添加了有关线程安全的新细节。如果您让 EJB 实例受线程限制,那么基本上您的类是线程安全的。否则在大多数情况下它们不是线程安全的(因为EntityManager 类不是线程安全的,大多数 EJB 中都在使用)
【解决方案2】:

我认为您所指的线程安全是不可重入子句

容器提供者的责任 Enterprise JavaBeans 3.2,最终发布会话 Bean 组件合同 2013 年 4 月 10 日下午 2:59 甲骨文

4.10.13 不可重入实例容器必须确保只有一个线程可以执行无状态或有状态会话 bean 随时实例。因此,有状态和无状态会话 bean 不必编码为可重入。该规则的一个含义 是应用程序不能对无状态或 有状态会话 bean 实例

您可以在无状态会话 bean 中忽略 EJB 编程限制,创建一堆线程并做一些非线程安全的事情。容器不会阻止你。由于线程管理冲突,这可能会导致各种错误。

容器只承诺一次只允许 1 个线程访问无状态 ejb。 (关于单例有一些不同的规则)

您是对的,如果将实例返回到示例中的连接可以建立的池中。因为当然被实例仍然存在。

如果您深入研究应用服务器文档,即 Glassfish EJB 池调优章节,您会发现默认是销毁对象实例而不是将它们返回到池中。与 JDBC 连接相同,它将被关闭和清理。重用是一种选择,在这种情况下,如果您尝试在 SSB 中创建状态,您可能会消耗一些额外的内存。如果实例在池中闲置,我认为不会对性能产生太大影响。

确切的池实现取决于应用服务器供应商,只要他们遵守规范即可。我想你会发现默认行为是在使用后销毁实例。这会导致托管资源被清理。

但是这一切都是无声的,在你的例子中,你试图在类中存储一个状态,连接字段。无需在无状态组件中创建状态,这不是此类组件的用途。 Java EE 架构的其他部分处理状态。 (实体、statefulbeans、JCA)

【讨论】:

  • 感谢您的解释。所以要恢复,注释@Stateless对使bean无状态没有影响(我知道它还有其他影响:由容器管理,利用事务等服务......)?最终是开发人员通过不使用实例变量来使其设计无状态......
  • 是的,@Stateless 是您告诉应用程序服务器的方式,它是一个无状态 bean。应用服务器相信你所写的确实是你想做的。
【解决方案3】:

1) 容器保证线程安全,这意味着单个线程一次可以访问给定的 SLSB。它与保证状态不会改变无关。顺便说一句,在 SLSB 中具有 可变 状态根本没有意义。

2) 您不想在 SLSB 的生命周期内保持连接,因为您正在有效地防止它返回池中而冒着耗尽的风险。

【讨论】:

  • 我不明白第二点,但你对第一点是正确的。但是,我的理解是即使容器也不能保证无状态,但它是开发者不使用实例变量
  • 取决于应用服务器是否正在池化您的 SLSB 实例,返回池时可能不会调用 PreDestroy 方法。这意味着连接没有返回到连接池。如果您达到 MaxConnection,那么您可能会在尝试获取新连接时无限期等待。
  • 感谢弗兰克的澄清
猜你喜欢
  • 1970-01-01
  • 2014-12-02
  • 1970-01-01
  • 2015-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-04
  • 1970-01-01
相关资源
最近更新 更多