使用默认的bean范围作为单例,当并发调用发生时不是很糟糕吗?

use*_*232 8 java spring multithreading

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

事实证明,当一些人试图同时发送电子邮件时,发生了一堆乱七八糟的事情.DB中的记录有错误的文件名.有些人根本没有获得文件名等.

我将问题归结为bean默认情况下将范围限定为单例.这意味着一堆线程可能同时搞乱了同一个实例.问题是如何解决这个问题.

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

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

我很迷惑.我该怎么办?

Boz*_*zho 12

单例范围的bean不应该保持任何状态 - 通常可以解决问题.如果您只将数据作为方法参数传递,并且不将其分配给字段,那么您将是安全的.


Era*_*rel 8

我同意@Bozho和@stivio的答案.

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

每种方法都有利弊:

  1. Singlton bean就像一个类似服务的类,有些人可能会说这不是一个好的面向对象的设计.
  2. 如果您不小心,将上下文传递给长链方法中的方法可能会使您的代码变得混乱.
  3. 原型bean可能会占用大量内存超过您的预期,并可能导致内存耗尽.你需要小心这些bean的生命周期.
  4. 原型豆可能会使您的设计更整洁.确保你不是通过多个线程重用bean.

在大多数简单的情况下,我倾向于使用服务方法.您还可以让这些单例bean创建一个处理对象,该对象可以保存计算的状态.这是一个可以为更复杂的场景提供最佳服务的解决方案.

编辑: 有些情况下,你有一个单独的bean取决于原型范围的bean,并且你想为每个方法调用一个新的原型bean实例.Spring提供了几种解决方案:

第一种是使用方法注入,如Spring参考文档中所述.我不太喜欢这种方法,因为它迫使你的班级变得抽象.

第二种方法是使用ServiceLocatorFactoryBean或您自己的工厂类(需要注入依赖项,并调用构造函数).在大多数情况下,这种方法非常有效,并且不会将您联系到Spring.

在某些情况下,您还希望原型bean具有运行时依赖性.我的一位好朋友在这里写了一篇很好的帖子:http://techo-ecco.com/blog/spring-prototype-scoped-beans-and-dependency-injection/.