3 回答
TA贡献1848条经验 获得超2个赞
不,没有传递关系。
JMM 背后的想法是定义 JVM 必须遵守的规则。如果 JVM 遵循这些规则,它们就有权根据需要重新排序和执行代码。
在您的示例中,第 2 次读取和第 3 次读取不相关 -例如使用synchronized或不会引入内存障碍volatile。因此,JVM 可以按如下方式执行它:
public Helper getHelper() {
final Helper toReturn = helper; // "3rd" read, reading null
if (helper == null) { // First read of helper
synchronized (this) {
if (helper == null) { // Second read of helper
helper = new Helper(42);
}
}
}
return toReturn; // Returning null
}
然后您的调用将返回一个空值。然而,会创建一个单例值。但是,后续调用可能仍会获得空值。
正如所建议的,使用 volatile 会引入新的内存屏障。另一种常见的解决方案是捕获读取的值并返回它。
public Helper getHelper() {
Helper singleton = helper;
if (singleton == null) {
synchronized (this) {
singleton = helper;
if (singleton == null) {
singleton = new Helper(42);
helper = singleton;
}
}
}
return singleton;
}
由于您依赖于局部变量,因此无需重新排序。一切都发生在同一个线程中。
TA贡献1775条经验 获得超8个赞
不,这些读取之间没有任何传递关系。synchornized只保证在同一个锁的同步块内所做的更改的可见性。在这种情况下,所有读取都不会使用同一个锁上的同步块,因此这是有缺陷的并且不保证可见性。
因为一旦字段被初始化就没有锁定,所以声明该字段是至关重要的volatile。这将确保可见性。
private volatile Helper helper = null;
TA贡献1852条经验 获得超1个赞
这一切都在这里解释https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories,问题很简单。
... 请注意,我们在此代码中对实例进行了多次读取,并且至少“读取 1”和“读取 3”是没有任何同步的读取...规范方面,如发生在一致性规则中所述,读取操作可以通过竞态观察无序写入。这是为每个读取操作决定的,无论其他哪些操作已经读取了相同的位置。在我们的例子中,这意味着即使“read 1”可以读取非空实例,代码然后继续返回它,然后它进行另一个活泼的读取,它可以读取一个空实例,它将被返回!
添加回答
举报