我有两个课程A
和B
:
class A {
private final String someData;
private B b;
public String getSomeData() { return someData; }
public B getB() {
if (b == null) {
b = new B(someData);
}
return b;
}
}
其中B
是不可变的,仅从A
的实例计算其数据。 A
具有不可变的语义,但它的内部结构是可变的(如hashCode
中的java.lang.String
)。
当我从两个不同的线程调用getB()
并且调用重叠时,我假设每个线程都有自己的B
实例。但由于B
的构造函数只获取不可变数据,因此B
的两个实例应该相等。
这是对的吗?如果没有,我是否必须使getB()
同步以使其成为线程安全的?
假设B实现equals(),它比较B的所有实例变量。对于hashCode()
答案 0 :(得分:7)
这不是线程安全的,因为你没有与volatile
或synchronized
创建任何“发生在之前”的关系,所以这两个线程都有可能彼此干涉。
问题是虽然b = new B(someData)
表示“为B
的实例分配足够的内存,然后在那里创建实例,然后将b
指向它”,系统被允许实现它“为B
的实例分配足够的内存,然后将b
指向它,然后创建实例”(因为,在单线程应用程序中,这是等效的)。所以在你的代码中,两个线程可以创建单独的实例但返回相同的实例,一个线程有可能在实例之前返回另一个线程的实例已完全初始化。
答案 1 :(得分:0)
For“但是由于B的构造函数只获取不可变数据,因此B的两个实例应该相等。” 正如您所理解的那样,它不是线程安全的,一个线程可能会获得未初始化的B实例(B为null或不一致的状态,其中某些数据尚未设置)其他线程可能会获得带有somedata set的b实例。
要解决此问题,您需要同步getB方法或使用带有双重检查锁定的同步块或某些非阻塞技术(如AtomicReference)。为了您的参考,我在这里添加了如何使用AtomicReference实现正确的threadSafe getB()方法的示例代码。
class A {
private final String someData = "somedata";
private AtomicReference<B> bRef;
public String getSomeData() { return someData; }
public B getB() {
if(bRef.get()== null){
synchronized (this){
if(bRef.get() == null)
bRef.compareAndSet(null,new B(someData));
}
}
return bRef.get();
}
}
class B{
public B(String someData) {
}
}