好吧,考虑下面给出的不可变类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;
}
}
现在我在类Immutable
中创建一个Sharable
的对象,其对象将由多个线程共享:
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
可以看到对{{1}的写入在写入obj
的字段之前发生的。因此,Immutable
可能会看到部分构造的Thread B
,它可能处于无效状态,其状态可能会在以后意外更改。
它是否使Immutable
非线程安全?
修改
好的,在查看了SO并进行了一些注释之后,我知道你可以安全地在构造对象之后在线程之间共享对不可变对象的引用 < / em>的。另外,正如@Makoto所提到的,通常需要声明包含其引用volatile的字段以确保可见性。此外,正如@PeterLawrey所述,将对不可变对象的引用声明为Immutable
会使该字段为final
答案 0 :(得分:7)
因此,线程B可以看到在写入不可变字段之前发生的对objas的写入。因此,线程B可以看到部分构造的Immutable,它可能处于无效状态,并且其状态可能会在以后意外地发生变化。
在Java 1.4中,这是真的。在Java 5.0及更高版本中,构造后的最终字段是线程安全的。
答案 1 :(得分:5)
你在这里描述的是两件不同的事情。首先,如果正在对它的实例执行操作,Immutable
线程安全。
线程安全在某种程度上是确保内存不会被另一个线程意外覆盖。在使用Immutable
的情况下,您永远不能覆盖其实例中包含的任何数据,因此您可以确信,在并发环境中,当您将Immutable
对象构造为对象时,它将是相同的当线程正在操纵它时。
你所拥有的是double-checked locking的实施方式。
你是正确的,线程A和线程B可能会在实例设置之前对其进行践踏,从而使对象Immutable
的整体不变性完全没有实际意义。
我认为解决此问题的方法是使用volatile
字段的obj
关键字,以便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
的实例对任何其他人都没有影响。