不稳定的保证和无序执行
重要编辑我知道“发生在”在两个作业发生的线程中我的问题是另一个线程是否可能当“a”仍然为空时读取“b”非空。所以我知道,如果您从与之前调用 setBothNonNull(...) 的线程相同的线程中调用 doIt() ,那么它不能抛出 NullPointerException。但是,如果一个线程从另一个线程调用 doIt() ,而不是调用 setBothNonNull(...) 呢?
请注意,这个问题仅与 易失性
关键字和 易失性
保证有关:它不与 synchronized
关键字相关(所以请不要回答“你必须使用同步”,因为我没有任何问题需要解决:我只是想了解关于out-of-的易失性
保证(或缺乏保证)订单执行)。
假设我们有一个包含两个 volatile 字符串引用的对象,这些引用被构造函数初始化为 null,并且我们只有一种方法来修改这两个字符串:通过调用 setBoth(...)< /em> 并且我们只能在之后将它们的引用设置为非空引用(只有构造函数才允许将它们设置为空)。
例如(这只是一个例子,还没有问题):
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”?
因为由于乱序(多)处理器和 易失性 工作方式,“b”可以在“a”之前分配?
易失性保证写入后的读取始终会看到最后写入的值,但这里存在无序“问题”,对吗? (再次强调,提出“问题”是为了尝试理解 volatile 关键字和 Java 内存模型的语义,而不是为了解决问题)。
IMPORTANT EDIT I know about the "happens before" in the thread where the two assignments are happening my question is would it be possible for another thread to be reading "b" non-null while "a" is still null. So I know that if you're calling doIt() from the same thread as the one where you previously called setBothNonNull(...) then it cannot throw a NullPointerException. But what if one is calling doIt() from another thread than the one calling setBothNonNull(...) ?
Note that this question is solely about the volatile
keyword and the volatile
guarantees: it is not about the synchronized
keyword (so please don't answer "you must use synchronize" for I don't have any issue to solve: I simply want to understand the volatile
guarantees (or lack of guarantees) regarding out-of-order execution).
Say we have an object containing two volatile
String references that are initialized to null by the constructor and that we have only one way to modify the two String: by calling setBoth(...) and that we can only set their references afterwards to non-null reference (only the constructor is allowed to set them to null).
For example (it's just an example, there's no question yet):
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;
}
}
In setBothNoNull(...), the line assigning the non-null parameter "a" appears before the line assigning the non-null parameter "b".
Then if I do this (once again, there's no question, the question is coming next):
doIt() {
if ( so.getB() != null ) {
System.out.println( so.getA().length );
}
}
Am I correct in my understanding that due to out-of-order execution I can get a NullPointerException?
In other words: there's no guarantee that because I read a non-null "b" I'll read a non-null "a"?
Because due to out-of-order (multi)processor and the way volatile
works "b" could be assigned before "a"?
volatile
guarantees that reads subsequent to a write shall always see the last written value, but here there's an out-of-order "issue" right? (once again, the "issue" is made on purpose to try to understand the semantics of the volatile
keyword and the Java Memory Model, not to solve a problem).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
不,您永远不会获得 NPE。这是因为
易失性
还具有引入先发生关系的记忆效应。换句话说,它将阻止重新排序上述语句,不会被重新排序,并且所有线程都会观察
a
的值one
fora
ifb 已经具有值
two
。以下是大卫·霍姆斯 (David Holmes) 对此的解释:
http://markmail.org/message/j7omtqqh6ypwshfv# query:+page:1+mid:34dnnukruu23ywzy+state:results
编辑(回应后续):
Holmes 的意思是,如果只有线程 A,编译器理论上可以进行重新排序。但是,还有其他线程,并且它们可以检测到重新排序。这就是为什么不允许编译器进行重新排序的原因。 java内存模型需要编译器专门确保没有线程会检测到这种重新排序。
不,您仍然永远不会有 NPE。易失性语义确实强加了线程间排序。这意味着,对于所有现有线程,
one
的分配发生在two
的分配之前。No, you will never get a NPE. This is because
volatile
also has the memory-effect of introducing a happens-before relationship. In other words, it will prevent reordering ofThe statements above, will not be re-ordered, and all threads will observe value
one
fora
ifb
already has valuetwo
.Here is a thread in which David Holmes explains this:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results
EDIT (response to the follow-up):
What Holmes is saying is, the compiler could in theory do a reorder if there were only thread A. However, there ARE other threads, and they CAN detect the reordering. That is why the compiler is NOT allowed to do that reordering. The java memory model requires the compiler specifically to make sure that no thread will ever detect such reordering.
No, you will still NEVER have a NPE.
volatile
semantics do impose inter-thread ordering. Meaning that, for all existing thread, assignment ofone
happens before the assignment oftwo
.假设分配给
a
和b
的值或者非空,我认为你的理解不正确。 JLS 是这样说的:和
定理
非正式证明
换句话说,将非空值写入
a
“发生在”读取a
的值 (XXX) 之前。 XXX 可以为 null 的唯一方法是,如果有其他某个操作将 null 写入a
,则 hb(write(a,non-null), write(a,XXX)) 和 hb(写(a,XXX),读(a,XXX))。根据问题定义这是不可能的,因此 XXX 不能为空。量子ED。解释 - JLS 指出 hb(...)(“发生在之前”)关系并不完全禁止重新排序。但是,如果 hb(xx,yy),则仅当生成的代码具有与原始序列相同的可观察效果时,才允许对操作 xx 和 yy 进行重新排序。
Assuming that the values assigned to
a
andb
or non-null, I think your understanding is not correct. The JLS says this:and
Theorem
Informal Proof
In other words, the write of a non-null value to
a
"happens-before" the read of the value (XXX) ofa
. The only way that XXX can be null, is if there was some other action writing null toa
such that hb(write(a,non-null), write(a,XXX)) and hb(write(a,XXX), read(a,XXX)). And this is impossible according to the problem definition, and therefore XXX cannot be null. QED.Explanation - the JLS states that the hb(...) ("happens-before") relationship does not totally forbid reordering. However, if hb(xx,yy), then reordering of actions xx and yy is only allowed if the resulting code has the same observable effect as the original sequence.
我发现下面的文章解释了在这种情况下 易失性 与同步具有相同的排序语义。 Java Volatile 很强大
I found the following post that explains that volatile has the same ordering semantics as synchronized in this case. Java Volatile is Powerful
虽然 Stephen C 的和已接受的答案很好并且几乎涵盖了它,但值得注意的是变量 a 不必是易失性的 - 并且您仍然不会得到 NPE。
这是因为,无论
a
是否为易失性,
。所以 Stephen C 的形式证明仍然适用,只是a = one
和b = Two
之间都会存在发生前关系a
不需要是易失性的。While Stephen C's and the accepted answers are good and pretty much cover it, it's worth making an important note that variable a doesn't have to be volatile - and you still won't get an NPE.
This is because there will be a happens-before relationship between
a = one
andb = two
, regardless of whethera
isvolatile
. So Stephen C's formal proof still applies, just no need fora
being volatile.我读了这个页面并发现了一个非- 挥发性和您的问题的非同步版本:
fro
可能会获取 1 或 3 作为a
的值,并且独立地可能会获取 2 或 4 作为的值b。
(我意识到这并不能回答你的问题,但它补充了你的问题。)
I read this page and found a non-volatile & non-synchronized version of your question:
fro
may obtain either 1 or 3 for the value ofa
, and independently may obtain either 2 or 4 for the value ofb
.(I realize this doesn't answer your question but it complements it.)