我有一个单独的Spring bean(默认范围)。因此,多个线程将使用一个实例。但是,我对线程安全有点困惑,显然所有Spring bean都是线程安全的,如果它们是无状态的,但我的bean不是无状态的,它有各种实例变量,每个请求/其他控制器/类使用。 / p>
这是我的单例bean的开头:
public class PcrfSimulator {
private final CustomGxSessionIdCacheImpl gxSessionIdCache = new CustomGxSessionIdCacheImpl();
private final PcrfRecord pcrfRec = new PcrfRecord();
private final ResponseConditions responseConditions = new ResponseConditions();
public CustomGxSessionIdCacheImpl getGxSessionIdCache() {
return gxSessionIdCache;
}
public ArrayList<Rule> getRules() {
return pcrfRec.getRules();
}
因此,上面的字段将由多个线程访问 - 是否足以将这些字段标记为volatile,或者我是否必须标记访问它们的方法(不仅有这个类,还有其他控制器) / classes)同步和使用wait / notify等?
非常感谢!
答案 0 :(得分:3)
volatile
无济于事。它只会确保该值真正更新。
易失手段(http://www.javamex.com/tutorials/synchronization_volatile.shtml):
- 此变量的值永远不会被线程本地缓存:所有读取和写入将直接进入“主存储器”;
- 对变量的访问就好像它包含在一个同步块中一样,自身同步。
使方法同步只有在控制流永远不会退出第一次写入和最后一次读取共享变量之间的(外部)同步块时才有用,并且所有共享变量只能在使用相同锁定的同步块中访问对象
因此,一般的解决方案是在这种情况下防止共享变量。使类不可变的一种简单方法是使用局部变量和方法参数而不是共享实例变量。
你写道:“如果Spring bean是无状态的,那么它们是线程安全的,但我的bean不是无状态的。” - 好的,主题将在上面的段落中讨论。
但是从您的代码中可以看出这不是问题!标有final
的变量因此它们是不可变的。如果该对象的字段以相同的方式运行(未更新或足以防止并发修改问题),则您没有可变共享变量。有时这称为“有效无状态”。这意味着值不会更改。所以这对于并发来说没有问题(因为并发问题是关于改变值)。
最后:如果字段(PcrfRecord
...)是无效的,则可以在不同的线程中使用此有效的无状态类,而不使用同步块。 (如果字段PcrfRecord
...不是无状态的,则类PcrfSimulator
不能被称为有效的无状态) - 但是这对于Spring来说,它是普通的Java。
顺便说一下:如果您的变量为final
,则无需进行volantile
。
答案 1 :(得分:3)
Spring本身确保在实例化,注入等之后正确发布你的bean。这意味着任何引用你的单例bean的线程至少会看到它在Spring上下文结束时的状态创建。
如果状态是不可变的,那么你没有任何事情可做。
如果单例的状态是可变的,则必须正确地同步对这种可变状态的访问。
答案 2 :(得分:0)
如果您在初始化后将其标记为单例,则您的类将不是线程安全的
手动“new
”的字段在创建bean时会发生一次,并且您将在内存中有一个像单例一样的实例,因此,您的线程共享CustomGxSessionIdCacheImpl,PcrfRecord等实例。
如果你可以让这些实例受到春天环境的控制,比如:
<bean id="customGxSessionIdCache" class="package.CustomGxSessionIdCacheImpl" scope="prototype">
并在PcrfSimulator中自动装配它们,如:
@Autowired
private final CustomGxSessionIdCacheImpl gxSessionIdCache
之后,只要您的代码访问gxSessionIdCache,spring就会分别为每个访问和每个线程创建一个新实例。 Singleton中的任何其他方法都必须使用synchronized
进行标记,因为这些方法对于多线程加入是开放的。春季单身人士是常规单身人士。
我认为,如果你根本没有任何状态,那就错了,那么一切都是线程安全的。如果您认为低级别,那么方法也会声明,即局部变量,如果多个线程访问这些,您也会感到头痛。
答案 3 :(得分:0)
已经确定,您的类不是线程安全的。原型范围是一种方法,但如果原型范围的bean自动连接到单例bean中,它仍然意味着只创建了一个原型bean实例,实际上也使它成为单例。
同步是另一种方法,但实际上只有在实例变量意味着在线程之间共享时才适用。但是,如果意图是实例变量应该唯一到每个线程,那么你应该改为ThreadLocal
。