在一篇文章中,我读到双止回阀锁坏了。因为编译器可以重新排序构造函数的顺序。
虽然人们通常会期望:
同样,在使用synchronized关键字时,代码重新排序永远不会按照JMM规范进行。
为什么编译器在同步()块中重新排序构造函数事件的顺序?
我在这里看到了很多关于DCL的文章,但我期待基于JMM和编译器重新排序的描述。
发布于 2013-08-06 03:42:27
编译器可以在同步块中重新排序指令。编译器可以在同步块之前(只要它们在前面)或之后(只要它们在后面)重新排序。但是,编译器不能自由地跨同步块边界(块开始或块结束)重新排序指令。
因此,完全在同步块内的构造和分配可以重新排序,没有正确同步的外部查看器可以在构造之前看到分配。
发布于 2013-08-06 04:32:22
为什么编译器在同步()块中重新排序构造函数事件的顺序?
它通常会这样做,以使代码运行得更快。
Java语言规范(JLS)指出,实现(例如编译器)可以在某些约束条件下重新排序指令和指令序列。
问题是,DCL的坏变体所作的假设超出了JLS所说的范围。其结果是执行JLS认为格式不太好。这是否显示为实际的错误/意外行为取决于编译器版本、硬件和其他各种事情。
但关键是编译器没有做错任何事情。错误在DCL代码中。
我只想补充一下,JIT编译器通常不会重新排序事件本身。它经常做的是消除硬件级内存读/写操作的限制。例如,通过删除特定内存写入被刷新到主存的约束,允许硬件延迟(甚至完全跳过)缓慢写入内存,只需将其写入L1缓存即可。相反,synchronized块的结束将强制对主内存进行缓存的写操作,产生额外的内存流量,(可能)一个管道停止。
发布于 2013-08-06 07:08:26
首先:
再次使用关键字时,代码重新排序永远不会按照JMM规范进行。
上述说法不完全准确。JMM定义了发生之前的关系。JLS只定义程序顺序并在顺序之前发生。见http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5。
它对指令的重新排序有影响。例如,
int x = 1;
synch(obj) {
y = 2;
}
int z = 3;现在,对于上面的代码,以下类型的重新排序是可能的。
synch(obj) {
int x = 1;
y = 2;
int z = 3;
}以上是一个有效的重新排序。
见http://jeremymanson.blogspot.in/2007/05/roach-motels-and-java-memory-model.html。
synch(obj) {
int z = 3;
y = 2;
int x = 1;
}以上也是一个有效的重新排序。
不可能的是,只有在获得锁之后,在释放锁之前才能执行y=2,这是JMM所保证的。另外,要查看来自另一个线程的正确效果,我们只需要在同步块中访问y。
现在我来到DCL。
请参阅DCL的代码。
if (singleton == null)
synch(obj) {
if(singleton == null) {
singleton == new Singleton()
}
}
return singleton;现在,上述方法的问题是:
singleton = new Singleton()不是一条指令。但是一套指令。在完全初始化构造函数之前,很可能先为单个引用分配对象引用。上述影响可以控制,使一个单一的人,因为不稳定,这也确定发生-之前的保证和能见度。
https://stackoverflow.com/questions/18071190
复制相似问题