在这个不符合规范的代码示例中,通过将Helper类的字段声明为final,使其成为不可变的。JMM保证不可变对象在对任何其他线程可见之前被完全构造。getHelper()方法中的块同步保证了所有可以看到helper字段的非空值的线程也将看到完全初始化的Helper对象。
public final class Helper {
private final int n;
public Helper(int n) {
this.n = n;
}
// Other fields and methods, all fields are final
}
final class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) { // First read of helper
synchronized (this) {
if (helper == null) { // Second read of helper
helper = new Helper(42);
}
}
}
return helper; // Third read of helper
}
}
然而,这段代码并不能保证在所有
虚拟机平台上都能成功,因为helper的第一次读取和第三次读取之间没有发生之前的关系。因此,第三次读取helper可能会获得一个陈旧的空值(可能是因为它的值被编译器缓存或重新排序),从而导致getHelper()方法返回一个空指针。
我不知道该怎么理解它。我同意在第一次读和第三次读之间没有关系发生,至少没有immediate关系。从某种意义上说,第一次读取必须发生在第二次读取之前,第二次读取必须发生在第三次读取之前,因此第一次读取必须发生在第三次读取之前,这是否存在可传递的发生之前关系
发布于 2018-10-15 02:14:33
不,没有传递关系。
JMM背后的思想是定义JVM必须遵守的规则。只要JVM遵循这些规则,它们就有权按自己的意愿重新排序和执行代码。
在您的示例中,第二次读取和第三次读取是不相关的-例如,使用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
}
然后,您的调用将返回空值。然而,已经创建了一个单例的值。但是,后续调用仍可能得到空值。
正如所建议的,使用易失性会引入新的内存障碍。另一种常见的解决方案是捕获读取值并返回它。
public Helper getHelper() {
Helper singleton = helper;
if (singleton == null) {
synchronized (this) {
singleton = helper;
if (singleton == null) {
singleton = new Helper(42);
helper = singleton;
}
}
}
return singleton;
}
因为您依赖于局部变量,所以没有什么需要重新排序的。所有的事情都发生在同一个线程中。
发布于 2018-10-15 01:06:27
在这种情况下,所有读操作都不使用同一锁上的同步块,因此这是有缺陷的,并且可见性不能得到保证。
由于字段初始化后不会锁定,因此将字段声明为volatile
非常重要。这将确保可见性。
private volatile Helper helper = null;
发布于 2018-10-15 02:11:12
这一切都在这里解释,https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories,问题很简单。
...注意,我们在这段代码中对实例进行了多次读取,至少“Read1”和“Read3”是没有任何同步 的读取...就规范而言,正如在一致性规则之前发生的那样,读取操作可以通过竞态观察无序写入。这是针对每个读取操作而决定的,而不管哪些其他操作已经读取了相同的位置。在我们的示例中,这意味着即使“Read1”可以读取非空实例,代码也会继续返回它,然后它会进行另一次快速读取,并且它可以读取将返回的空实例!
https://stackoverflow.com/questions/52805020
复制相似问题