我想了解是否需要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
以确保其内存可见性?
不,您不能保证您将看到共享数据的toShare
字段的所有更新。这是因为您的共享数据不使用任何同步构造来保证其可见性或跨线程可通过它访问的引用的可见性。这使得它可以在编译器和硬件级别上进行大量优化。
您可以安全地更改您的toShare
字段以引用String
(这对于您的所有目的而言也是不可变的)并且您可能(并且正确地)对其更新可见性感到更加不安。
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
附:还有一个问题 - 如果sharedData
(而不是safe
)成为volatile
会发生什么?根据JMM会发生什么?
答案是否定的,需要使用volatile
或任何其他方式(例如,将synchronized
关键字添加到签名获取和设置)以生成Happens / Before边缘。最终字段语义仅保证如果有人看到指向该类实例的指针,则所有最终字段的值都根据构造函数设置完成: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/
似乎JMM应该处理发布不可变对象的可见性问题,至少在实践中的并发性,3.5.2不可变对象和安全初始化中说的是:
由于不可变对象非常重要,因此JavaMemory Model为共享不可变对象提供了初始化安全性的特殊保证。正如我们所见,对象引用变得对另一个线程可见并不一定意味着该对象的状态对于消费线程是可见的。为了保证对象状态的一致视图,需要同步。
另一方面,即使不使用同步来发布对象引用,也可以安全地访问不可变对象。为保证初始化安全性,必须满足所有不可变性的要求:不可修改的状态,所有字段都是最终的,以及正确的构造。
任何线程都可以安全地使用不可变对象而无需额外的同步,即使不使用同步来发布它们也是如此。
以下章节3.5.3安全发布惯用语指出仅使用以下方法对非不可变对象进行安全发布: