简体   繁体   English

使用不可变数据进行延迟初始化是否始终是线程安全的?

[英]Is lazy initialization with immutable data always thread-safe?

I have two classes A and B : 我有两个AB类:

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. 不是线程安全的,因为你还没有创建任何“之前发生”有关系volatilesynchronized ,所以这是可能的两个线程互相干扰。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM