[英]Is lazy initialization with immutable data always thread-safe?
I have two classes A
and B
: 我有两个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;
}
}
where B
is immutable and computes its data only from an instance of A
. 其中B
是不可变的,仅从A
的实例计算其数据。 A
has immutable semantics, but it's internals are mutable (like hashCode
in java.lang.String
). A
具有不可变的语义,但它的内部结构是可变的(如java.lang.String
hashCode
)。
When I call getB()
from two different threads, and the calls overlap, I assume each thread gets its own instance of B
. 当我从两个不同的线程调用getB()
并且调用重叠时,我假设每个线程都有自己的B
实例。 But since the constructor of B
gets only immutable data, the two instances of B
should be equal. 但由于B
的构造函数只获取不可变数据,因此B
的两个实例应该相等。
Is that correct? 那是对的吗? If not, must I make getB()
synchronized to make it thread-safe? 如果没有,我必须使getB()
同步以使其线程安全吗?
Assume that B implements equals(), which compares all instance variables of B. Same for hashCode() 假设B实现equals(),它比较B的所有实例变量。对于hashCode()
This is not thread-safe, because you haven't created any "happens-before" relationships with volatile
or synchronized
, so it's possible for the two threads to interfere with each other. 这不是线程安全的,因为你还没有创建任何“之前发生”有关系volatile
或synchronized
,所以这是可能的两个线程互相干扰。
The problem is that although b = new B(someData)
means "allocate enough memory for an instance of B
, then create the instance there, then point b
to it", the system is allowed to implement it as "allocate enough memory for an instance of B
, then point b
to it, then create the instance" (since, in a single-threaded app, that's equivalent). 问题是虽然b = new B(someData)
意味着“为B
的实例分配足够的内存,然后在那里创建实例,然后将b
指向它”,系统可以将其实现为“为其分配足够的内存” B
实例,然后将b
指向它,然后创建实例“(因为,在单线程应用程序中,这是等效的)。 So in your code, where two threads can create separate instances but return the same instance, there's a chance that one thread will return the other thread's instance before the instance is fully initialized . 因此,在您的代码中,两个线程可以创建单独的实例但返回相同的实例,一个线程有可能在实例完全初始化之前返回另一个线程的实例。
For "But since the constructor of B gets only immutable data, the two instances of B should be equal." 对于“但是因为B的构造函数只获取不可变数据,所以B的两个实例应该相等。” As you understand its not thread safe, one thread might get un-initialized B instance (B as null or inconsistent state where somedata not yet set) others might get b instance with somedata set. 正如您所理解的那样,它不是线程安全的,一个线程可能会获得未初始化的B实例(B为null或不一致状态,其中某些数据尚未设置)其他线程可能获得带有somedata set的b实例。
To fix this you need synchronized getB method or use synchronized block with double-check lock or some non-blocking technique like AtomicReference. 要解决此问题,您需要同步getB方法或使用带有双重检查锁定的同步块或某些非阻塞技术(如AtomicReference)。 For you reference I am adding here sample code for how to achieve the correct threadSafe getB() method using 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) {
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.