将默认的 bean 作用域设置为单例,发生并发调用时不会很糟糕吗?



我声明了一个Spring bean,它每隔几秒轮询一次我的电子邮件服务器。如果有邮件,它将获取邮件,并尝试提取其中的任何附加文件。然后将这些文件提交给安全存储它们的上传器。上传程序也被声明为Spring bean。第三个bean将电子邮件的发件人与文件的文件名关联起来,并将其存储在数据库中。

事实证明,当几个人试图同时发送电子邮件时,会发生一堆混乱的事情。数据库中的记录有错误的文件名。有些根本没有得到文件名,等等。

我将问题归因于bean默认情况下作用域为单例的事实。这意味着一堆线程可能会在同一时间搞砸同一个实例。问题是如何解决这个问题。

如果我同步所有的敏感方法,那么所有的线程将会堆积起来并等待彼此,这有点违背多线程的整个思想。

另一方面,将bean的作用域限定为"request"将创建每个bean的新实例,如果我们谈论内存消耗和线程调度,这也不是很好

我很困惑。我该怎么办?

单例作用域的bean不应该保持任何状态——这通常可以解决问题。如果您只将数据作为方法参数传递,而不将其分配给字段,那么您将是安全的。

我同意@Bozho和@stivio的回答。

首选选项是在单例作用域bean中传递无存储状态,并将上下文对象传递给方法,或者使用为每个处理周期创建的原型/请求作用域bean。通过选择其中一种方法,通常可以避免同步,从而获得更高的性能,同时避免死锁。只要确保你没有修改任何共享状态,比如静态成员。

每种方法都有优点和缺点:

  1. 单例bean充当类似服务的类,有些人可能会说这不是一个好的面向对象设计。
  2. 如果你不小心,将上下文传递给一长串方法中的方法可能会使你的代码混乱。
  3. 原型bean可能占用大量内存的时间比您预期的要长,并且可能导致内存耗尽。您需要注意这些bean的生命周期。
  4. 原型bean可以使您的设计更整洁。但是,请确保您没有在多个线程中重用bean。
在大多数简单的情况下,我倾向于使用服务方法。您还可以让这些单例bean创建一个处理对象,该对象可以保存它的计算状态。对于更复杂的场景,这可能是一个最适合您的解决方案。 编辑:

在某些情况下,您有一个依赖于原型作用域bean的单例bean,并且您希望为每个方法调用一个原型bean的新实例。Spring为此提供了几种解决方案:

第一个是使用方法注入,如Spring参考文档中所述。我真的不喜欢这种方法,因为它迫使你的类是抽象的。

第二种是使用ServiceLocatorFactoryBean,或者您自己的工厂类(需要注入依赖项,并调用构造函数)。这种方法在大多数情况下工作得很好,并且不会将您与Spring耦合。

在某些情况下,您还希望原型bean具有运行时依赖项。我的一个好朋友写了一篇关于这个的好文章:http://techo-ecco.com/blog/spring-prototype-scoped-beans-and-dependency-injection/.

否则就声明你的bean作为请求,不用担心内存消耗,垃圾收集会清除它,只要有足够的内存就不会有性能问题。

抽象地说:如果您正在使用Spring Integration,那么您应该根据消息本身构建代码。例如,所有重要的状态都应该与消息一起传播。这使得通过添加更多spring Integration实例来处理负载的扩展变得非常简单。Spring Integration中唯一的状态(实际上)是针对像聚合器这样的组件,它等待并收集具有相关性的消息。在这种情况下,您可以委托给后台存储(如MongoDB)来处理这些消息的存储,这当然是线程安全的。

更一般地说,这是一个阶段事件驱动架构的例子——组件必须无状态地(N(1),不管有多少消息)处理消息,然后将它们转发到一个通道上,供另一个不知道消息来自哪个组件的组件使用。

如果您在使用Spring Integration时遇到线程安全问题,您可能正在做一些与预期不同的事情,并且可能值得重新审视您的方法…

单例模式应该是有状态的,并且是线程安全的。

如果单例是无状态的,它是有状态的退化情况,线程安全通常是正确的。但是单身又有什么意义呢?只要每次有人请求就创建一个新的。

如果一个实例是有状态的而不是线程安全的,那么它就不能是单例的;每个线程应该独占一个不同的实例。

最新更新