我读过一个类似的问题,关于如何在不使用volatile的情况下仔细检查锁定: Double-checked locking without volatile
以及以下代码:
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
但是我的问题是,为什么不简单地删除volatile
属性。
根据我的经验,volatile
是用来告诉编译器不要重新排序“写入field
对象”和“构造FieldType
”之间的顺序。 (请参阅http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)
但是在这种情况下,第二次检查后,field = result
应该先于result = computeFieldValue()
,然后发生。因此,编译器不得重新排序序列。
因此,field
无法“过早”初始化。
那么,我对波动性或之前发生的理解怎么了?
更新
首先,这是一个不正确的示例:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
不起作用的最明显原因是
初始化Helper对象的写操作和对helper字段的写操作可以完成或顺序混乱
换句话说,helper
可能会得到一个不完全构造的Helper
对象。
volatile
之所以在这里起作用,是因为以下两个含义:
JDK5和更高版本扩展了volatile的语义,以便系统不允许相对于任何先前的读取或写入对volatile的写入进行重新排序,并且对之后的任何读取均不能对volatile的读取进行重新排序或写。
我将field = result = computeFieldValue();
和result = computeFieldValue(); field = result;
分开。那么显然存在先发生关系,对吧?
所以我正在考虑使用field = result = new FieldType()
这样的局部临时变量是否可以替代内存屏障效应?