Java内存模型和重新排序操作
作者:互联网
我的问题是针对帖子的:
https://shipilev.net/blog/2014/safe-public-construction/
public class UnsafeDCLFactory {
private Singleton instance;
public Singleton get() {
if (instance == null) { // read 1, check 1
synchronized (this) {
if (instance == null) { // read 2, check 2
instance = new Singleton(); // store
}
}
}
return instance; // read 3
}
}
并写道:
Notice that we do several reads of instance in this code, and at least “read 1” and “read 3” are the reads without any
synchronization — that is, those reads are racy. One of the intents of
the Java Memory Model is to allow reorderings for ordinary reads,
otherwise the performance costs would be prohibitive.
Specification-wise, as mentioned in happens-before consistency rules,
a read action can observe the unordered write via the race. This is
decided for each read action, regardless what other actions have
already read the same location. In our example, that means that even
though “read 1” could read non-null instance, the code then moves on
to returning it, then it does another racy read, and it can read a
null instance, which would be returned!
我不明白.我同意编译器显然可以重新排序内存操作.但是,这样做,编译器必须从单线程的点视图保留原始程序的行为.
在上面的例子中,read 1读取非null.读取3读取null.这意味着编译器重新排序了读取3并读取优先级中的实例以读取1(我们可以跳过CPU重新排序,因为该帖子引发了Java内存模型).
但是,在我看来,由于存储操作,读取3不能超过读取1 – 我的意思是instance = new Singleton();
毕竟,存在数据依赖性,这意味着编译器不能重新排序带有存储的读取3指令,因为它改变了程序的含义(甚至是单线程).编译器也无法更改读取1的顺序,因为它必须在存储之前.否则,单线程程序的语义是不同的.
因此,订单必须是:读取1 – >商店 – >阅读3
你怎么看待这件事?
附:发表什么意味着什么?特别是,不安全地发布一些东西意味着什么?
这是对@Aleksey Shipilev的答案的重新回答.
Let me say this again — failure to construct the example does not disprove the rule.
是的,很明显.
And Java Memory Model allows returning null on the second read.
我同意这一点.我并不声称它不允许. (这可能是因为数据竞争 – 是的,它们是邪恶的).我声称阅读3不能超过阅读1.我知道你是对的,但我想明白这一点.我仍然声称Java编译器不能生成这样的字节码,读取3接管读取1.我看到read3可以读取null因为数据竞争,但我无法想象如何读取1读取非null和读取3读取null而由于数据依赖性,读取3不能超过读取1.
(我们这里不考虑硬件(CPU)级别的内存排序)
解决方法:
But, doing it, the compiler has to preserve the behaviuor of the
original program from the one-thread’s point view.
不.它必须保留语言规范的要求.在这种情况下,JMM.如果某些转换在JMM下是合法的,则可以执行. “单线程的观点”不是规范性语言,规范是.
Java内存模型允许在第二次读取时返回null.如果你不能构造实现它的实际转换,那并不意味着这种转换是不可能的.让我再说一遍 – 未能构建这个例子并不反驳这条规则.现在您可以在“Benign Data Races are Resilient”中看到示例转换,并在那里阅读此段落:
This may appear counter-intuitive: if we read null from instance, we
take a corrective action with storing new instance, and that’s it.
Indeed, if we have the intervening store to instance, we cannot see
the default value, and we can only see that store (since it
happens-before us in all conforming executions where first read
returned null), or the store from the other thread (which is not null,
just a different object). But the interesting behavior unfolds when we
don’t read null from the instance on the first read. No intervening
store takes place. The second read tries to read again, and being racy
as it is, may read null. Ouch.
也就是说,您可以轻松地将程序转换为在没有插入写入的情况下公开路径,并且通过简单的读取重新排序提供“反直觉”结果.字节码并不是该程序唯一适用的“转型”.上面的链接概述了在没有存储数据依赖性的情况下公开代码路径的编译器转换.即这两个程序略有不同:
// Case 1
Object o1 = instance;
instance = new Object();
Object o2 = instance;
// Case 2
Object o1 = instance;
if (o1 == null)
instance = new Object();
Object o2 = instance;
在“案例2”中,有一个避免实例存储的路径 – 因为程序现在有两条路径,由于分支 – 优化转换可以暴露它.
这是一个可能出错的人为例子.在实际程序中,控制和数据流要复杂得多,优化转换允许(并最终)产生类似的结果,因为它们在一组派生规则上运行,如“没有同步,没有数据依赖 – 免费在公开有趣行为的一公吨变换之后“移动东西”.
数据竞赛是邪恶的.
标签:java,memory-barriers,java-memory-model 来源: https://codeday.me/bug/20190627/1307414.html