我想了解发布不可变对象是否需要volatile
。
例如,假设我们有一个不可变对象A
:
// class A is immutable
class A {
final int field1;
final int field2;
public A(int f1, int f2) {
field1 = f1;
field2 = f2;
}
}
然后我们有一个可以从不同线程访问的类B
。它包含对类A
的对象的引用:
// class B publishes object of class A through a public filed
class B {
private /* volatile? */ A toShare;
// this getter might be called from different threads
public A getA(){
return toShare;
}
// this might be called from different threads
public void setA(num1, num2) {
toShare = new A(num1, num2);
}
}
根据我的阅读,似乎可以通过任何方式安全地发布不可变对象,因此这意味着我们不需要将toShare
声明为volatile
确保其内存可见性?
答案 0 :(得分:2)
不,您无法保证您会看到共享数据的toShare
字段的所有更新。这是因为您的共享数据不使用任何同步构造来保证其可见性或跨线程可通过它访问的引用的可见性。这使得它可以在编译器和硬件级别上进行大量优化。
您可以安全地更改toShare
字段以引用String
(对于您的所有目的而言也是不可变的),您可能(并且正确地)对其更新可见性感到更加不安。 / p>
Here您可以看到我创建的一个基本示例,它可以显示如何更新丢失,而无需任何其他措施来发布对不可变对象的引用的更改。我使用JDK 8u65和英特尔®酷睿™i5-2557M上的-server
JVM标志运行它,忽略可能抛出的NullPointerException
并看到以下结果:
safe
没有volatile
,则第二个线程不会终止,因为它没有看到第一个线程所做的许多更改控制台输出:
[T1] Shared data visible here is 2147483647
safe
更改为volatile
时,第二个线程将与第一个线程一起终止控制台输出:
[T1] Shared data visible here is 2147483647
[T2] Last read value here is 2147483646
[T2] Shared data visible here is 2147483647
P.S。还有一个问题 - 如果sharedData
(而非safe
)成为volatile
会怎样?根据JMM会发生什么?
答案 1 :(得分:0)
答案是否,需要使用volatile
或任何其他方式(例如,将synchronized
关键字添加到签名获取和设置)才能发生/前缘。最终字段语义仅保证如果有人看到指向该类实例的指针,则所有最终字段的值都根据构造函数在完成时设置:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
这并没有说明参考本身的可见性。由于您的示例使用非最终字段
私人A toShare;
您必须使用volatile
或synchronized
部分或java.util.concurrent.locks.Locks或AtomicReference等来关注字段的可见性,以启动/保证缓存同步。一些有用的东西,BTW,关于决赛和安全出版http://shipilev.net/blog/2014/safe-public-construction/
答案 2 :(得分:0)
JMM似乎应该考虑发布不可变对象的可见性问题,至少应该是《并发实践》 3.5.2不可变对象和安全初始化中所说的:
由于不可变对象非常重要,因此JavaMemory Model提供了初始化安全的特殊保证 用于共享不可变的对象。如我们所见,对象引用对另一个线程不可见 必须表示该对象的状态对于使用线程是可见的。为了保证一致的看法 对象状态的同步是必需的。
另一方面,即使不使用同步来发布不可修改的对象,也可以安全地访问它们 对象参考。为了保持初始化安全性,必须满足不变性的所有要求: 状态不可修改,所有字段都是最终的,正确的构造。不可变对象可以在没有附加同步的情况下安全地用于任何线程,即使同步是 不习惯发布它们。
以下章节 3.5.3安全发布惯用语指出,仅非不变对象需要使用以下方法进行安全发布: