重要编辑我知道“发生在之前”在两个任务发生的线程中我的问题是 另一个 em> 线程正在读取“b”非空,而“a”仍为空。所以我知道,如果您从与之前调用 setBothNonNull(...) 的线程相同的线程中调用 doIt(),那么它不会抛出 NullPointerException。但是,如果一个调用 doIt() 从另一个线程 而不是调用 setBothNonNull(...) 呢?
请注意,此问题仅与 volatile
关键字有关,而 volatile
保证:它不是与 synchronized
关键字(所以请不要回答“你必须使用同步”,因为我没有任何问题要解决:我只是想了解关于 out 的 volatile
保证(或缺乏保证)顺序执行)。
假设我们有一个包含两个 volatile
字符串引用的对象,这些引用被构造函数初始化为 null,并且我们只有一种方法来修改这两个字符串:通过调用 setBoth(.. .) 并且我们只能在之后将它们的引用设置为非空引用(仅允许构造函数将它们设置为空)。
例如(这只是一个例子,还没有问题):
public class SO {
private volatile String a;
private volatile String b;
public SO() {
a = null;
b = null;
}
public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
a = one;
b = two;
}
public String getA() {
return a;
}
public String getB() {
return b;
}
}
在setBothNoNull(...)中,分配非空参数“a”的行出现在分配非空参数“b”的行之前。
那么如果我这样做(再一次,没有问题,问题来了):
doIt() {
if ( so.getB() != null ) {
System.out.println( so.getA().length );
}
}
我的理解是否正确,即由于乱序执行我会得到 NullPointerException?
换句话说:不能保证因为我读到了一个非空的“b”,我就会读到一个非空的“a”?
由于无序(多)处理器和 volatile
的工作方式“b”可以在“a”之前分配?
volatile
保证在写入之后的读取将始终看到最后写入的值,但是这里有一个无序的“问题”对吗? (再一次,“问题”是故意试图理解 volatile
关键字和 Java 内存模型的语义,而不是解决问题。
最佳答案
不,您永远不会获得 NPE。这是因为 volatile
还具有引入happens-before 关系的内存效应。换句话说,它将防止重新排序
a = one;
b = two;
上面的语句,不会被重新排序,如果 b
已经有值 两个
。
David Holmes 对此进行了解释:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results
编辑(响应后续): Holmes 的意思是,如果只有线程 A,编译器理论上可以进行重新排序。但是,还有其他线程,它们可以检测到重新排序。这就是为什么不允许编译器进行重新排序的原因。 Java 内存模型需要编译器专门确保没有线程会检测到这种重新排序。
But what if one is calling doIt() from another thread than the one calling setBothNonNull(...) ?
不,你永远不会有 NPE。 volatile
语义确实强加了线程间的顺序。这意味着,对于所有现有线程,one
的分配发生在 two
的分配之前。
关于java - 不稳定的保证和乱序执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2441279/