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

时间:2014-03-09 23:42:56

标签: java multithreading thread-safety immutability lazy-initialization

我有两个课程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;
    }
}

其中B是不可变的,仅从A的实例计算其数据。 A具有不可变的语义,但它的内部结构是可变的(如hashCode中的java.lang.String)。

当我从两个不同的线程调用getB()并且调用重叠时,我假设每个线程都有自己的B实例。但由于B的构造函数只获取不可变数据,因此B的两个实例应该相等。

这是对的吗?如果没有,我是否必须使getB()同步以使其成为线程安全的?

假设B实现equals(),它比较B的所有实例变量。对于hashCode()

2 个答案:

答案 0 :(得分:7)

是线程安全的,因为你没有与volatilesynchronized创建任何“发生在之前”的关系,所以这两个线程都有可能彼此干涉。

问题是虽然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) {

    }
}