那么,考虑如下所示的不可变类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 A
将obj
视为null
,并进入同步块并创建对象。现在,由于Java内存模型(JMM)允许多个线程在对象初始化开始之后但结束之前观察对象。因此,Thread B
可以看到对obj
的写入发生在对Immutable
字段的写入之前。因此,Thread B
可以看到部分构造的Immutable
很可能处于无效状态,并且其状态可能在稍后意外更改。
这不是使Immutable
非线程安全吗?
编辑
好了,在对SO进行了大量的查找并仔细研究了一些注释之后,我知道了在对象构造之后,可以安全地在线程之间共享对不可变对象的引用。此外,正如@Makoto所提到的,通常需要声明包含其引用的字段为volatile,以确保可见性。此外,正如@PeterLawrey所述,将对不可变对象的引用声明为final
将使该字段为thread-safe
在Java 1.4中,因此,线程B可以看到对objas的写入发生在对不可变字段的写入之前。因此,线程B可以看到部分构造的不可变,它很可能处于无效状态,并且其状态可能在以后意外更改。
是正确的。在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
的实例也不会影响其他的。