不变性保证线程安全吗?



那么,考虑如下所示的不可变类Immutable:

public final class Immutable
{
    final int x;
    final int y;
    public Immutable(int x,int y)
    {
        this.x = x;
        this.y = y;
    }
    //Setters
    public int getX()
    {
        return this.x;
    }
    public int getY()
    {
        return this.y;
    }
}

现在我在类Sharable中创建一个Immutable的对象,该对象将由多个线程共享:

public class Sharable
{
    private static Immutable obj;
    public static Immutable getImmutableObject()
    {
        if (obj == null) --->(1)
        {
            synchronized(this)
            {
                if(obj == null)
                {
                    obj = new Immutable(20,30); ---> (2)
                }
            }
        }
        return obj; ---> (3)
    }
}

Thread Aobj视为null,并进入同步块并创建对象。现在,由于Java内存模型(JMM)允许多个线程在对象初始化开始之后但结束之前观察对象。因此,Thread B可以看到对obj的写入发生在对Immutable字段的写入之前。因此,Thread B可以看到部分构造的Immutable很可能处于无效状态,并且其状态可能在稍后意外更改。

这不是使Immutable非线程安全吗?


编辑
好了,在对SO进行了大量的查找并仔细研究了一些注释之后,我知道了在对象构造之后,可以安全地在线程之间共享对不可变对象的引用。此外,正如@Makoto所提到的,通常需要声明包含其引用的字段为volatile,以确保可见性。此外,正如@PeterLawrey所述,将对不可变对象的引用声明为final将使该字段为thread-safe

因此,线程B可以看到对objas的写入发生在对不可变字段的写入之前。因此,线程B可以看到部分构造的不可变,它很可能处于无效状态,并且其状态可能在以后意外更改。

在Java 1.4中,

是正确的。在Java 5.0及以上版本中,final字段在构造后是线程安全的。

你在这里描述的是两件不同的事情。首先,Immutable 线程安全的,如果操作是在的一个实例上进行的。在某种程度上,线程安全就是确保内存不会被另一个线程意外地覆盖。只要使用Immutable,你就永远不能覆盖它的实例中包含的任何数据,所以你可以确信,在并发环境中,Immutable对象在你构造它的时候和在线程操作它的时候是一样的。

你在这里得到的是一个错误的双重检查锁定实现。

你是对的,线程A和线程B可能会在实例设置之前践踏它,从而使对象Immutable的整个不变性完全没有意义。

我认为解决这个问题的方法是为你的obj字段使用volatile关键字,这样Java(> 1.5)就会尊重单例的预期用途,并且不允许线程覆盖obj的内容。

现在,仔细阅读一下,你会发现一个不可变的单例需要两个静态数据才能存在,这似乎有点不靠谱。这似乎更适合工厂使用。

public class Sharable {
    private Sharable() {
    }
    public static Immutable getImmutableInstance(int a, int b) {
        return new Immutable(a, b);
    }
}

你得到的Immutable的每个实例将真正的是不可变的——创建一个新的Immutable不会影响其他的,使用Immutable的实例也不会影响其他的。

最新更新