In some article I read that double check locking is broken. As the compiler can reorder the sequence of constructors.
在一些文章中,我读到双重检查锁定被打破。因为编译器可以重新排序构造函数的序列。
- Ss allocate memory for an object
- Then return the address to a reference variable
- Then initialize the state of the object
Ss为对象分配内存
然后将地址返回给引用变量
然后初始化对象的状态
While typically one would expect:
虽然通常人们会期望:
- It should be as allocated memory for the object
- then initialize the state of object
- then return the address to the reference variable.
它应该是对象的已分配内存
然后初始化对象的状态
然后将地址返回给引用变量。
Again, when using the synchronized
keyword, the code reorder never happens as per JMM specification.
同样,在使用synchronized关键字时,代码重新排序永远不会按照JMM规范发生。
Why is compiler reordering the sequence of constructor events when it is inside the synchronized() block?
为什么编译器在synchronized()块内部重新排序构造函数事件的序列?
I saw a lot of posts here about DCL, but I am expecting the description based on JMM and compiler reordering.
我在这里看到很多关于DCL的帖子,但我期待基于JMM和编译器重新排序的描述。
3 个解决方案
#1
3
The compiler is free to reorder instructions within a synchronized block. And the compiler is free to reorder instructions before (as long as they stay before) or after (as long as they stay after) the synchronized block. However, the compiler is not free to reorder instructions across the synchronized block boundaries (block start or block end).
编译器可以在同步块中重新排序指令。并且编译器可以*地重新排序指令(只要它们保持之前)或之后(只要它们保持在之后)同步块。但是,编译器无法在同步块边界(块开始或块结束)上重新排序指令。
Thus, the construction and assignment which are wholly within the synchronized block can be reordered, and an outside viewer which has not correctly synchronized can see the assignment before the construction.
因此,完全在同步块内的构造和分配可以被重新排序,并且未正确同步的外部观察者可以在构造之前看到分配。
#2
0
First of all:
首先:
Again when using the synchronized keyword, the code reorder never happens as per the JMM specification.
再次使用synchronized关键字时,代码重新排序永远不会按照JMM规范发生。
The above statement is not fully accurate. The JMM defined the happens-before relationship. The JLS only defines the program order and happens-before order. See 17.4.5. Happens-before Order.
以上陈述并不完全准确。 JMM定义了之前发生的关系。 JLS仅定义程序顺序并在订单之前发生。见17.4.5。发生在订单之前。
It has effects on the reordering of instructions. For example,
它对指令的重新排序有影响。例如,
int x = 1;
synch(obj) {
y = 2;
}
int z = 3;
Now for the above piece of code, the below types of reordering are possible.
现在对于上面的代码,可以进行以下类型的重新排序。
synch(obj) {
int x = 1;
y = 2;
int z = 3;
}
The above is a valid reordering.
以上是有效的重新排序。
See Roach Motels and The Java Memory Model.
请参阅Roach Motels和Java Memory Model。
synch(obj) {
int z = 3;
y = 2;
int x = 1;
}
The above is also a valid reordering.
以上也是有效的重新排序。
What is not possible is that y=2 will only be executed after the lock has been acquired and before the lock is released this is what guaranteed given by JMM. Also to see the correct effects from another thread, we need to access y inside the synchronized block only.
不可能的是,y = 2只会在获取锁定之后和锁定释放之前执行,这是JMM给出的保证。另外,为了从另一个线程看到正确的效果,我们只需要访问synchronized块内的y。
Now I come to DCL.
现在我来到DCL。
See the code of DCL.
请参阅DCL的代码。
if (singleton == null)
synch(obj) {
if(singleton == null) {
singleton == new Singleton()
}
}
return singleton;
Now the problem in the above approach is:
现在上述方法的问题是:
-
singleton = new Singleton()
is not a single instruction. But a set of instructions. It is quite possible that a singleton reference is assigned an object reference first, before fully initializing the constructor.singleton = new Singleton()不是单个指令。但是一套说明。在完全初始化构造函数之前,很可能首先为单例引用分配对象引用。
-
So if 1 happens then it's quite possible the other thread reads a singleton reference as a non null and thus is seeing a partially constructed object.
因此,如果1发生,则很可能另一个线程将单个引用读取为非null,因此看到部分构造的对象。
The above effects can be controlled by making a singleton as volatile which also establishes happens-before guarantees and visibility.
可以通过将单例设置为易失性来控制上述效果,这也可以建立先发生后的保证和可见性。
#3
0
Why is compiler reordering the sequence of constructor events when it is inside the synchronized() block?
为什么编译器在synchronized()块内部重新排序构造函数事件的序列?
It would typically do this to make the code run faster.
通常这样做可以使代码运行得更快。
The Java Language Specification (JLS) says that the implementation (for example, the compiler) is allowed to reorder instructions and sequences of instructions subject to certain constraints.
Java语言规范(JLS)表示允许实现(例如,编译器)重新排序受特定约束的指令和指令序列。
The problem is that the broken variants of DCL make assumptions that fall outside of what the JLS says can be made. The result is an execution that the JLS says is not well-formed. Whether this manifests itself as an actual bug / unexpected behaviour depends on the compiler version, the hardware and various other things.
问题在于DCL的破坏变体使得假设超出了JLS所说的范围。结果是JLS所说的执行不正确。这是否表现为实际的错误/意外行为取决于编译器版本,硬件和其他各种因素。
But the point is that the compiler isn't doing anything wrong. The fault is in the DCL code.
但重点是编译器没有做错任何事情。故障在DCL代码中。
I just want to add that the JIT compiler is often not reordering the events per se. what it is often doing is removing constraints on hardware-level memory read/write actions. For example, by removing the constraint that a particular memory write is flushed to main memory, you allow the hardware to defer (or even skip entirely) a slow write-to-memory, and just write to the L1 cache. By contrast, the end of a synchronized
block will force the cached writes to main memory, incurring extra memory traffic and (probably) a pipeline stalls.
我只想补充一点,JIT编译器通常不会重新排序事件本身。它经常做的是删除硬件级内存读/写操作的约束。例如,通过删除特定内存写入刷新到主内存的约束,您允许硬件推迟(甚至完全跳过)慢速写入内存,并只写入L1缓存。相比之下,同步块的结束将强制缓存写入主内存,导致额外的内存流量和(可能)管道停顿。
#1
3
The compiler is free to reorder instructions within a synchronized block. And the compiler is free to reorder instructions before (as long as they stay before) or after (as long as they stay after) the synchronized block. However, the compiler is not free to reorder instructions across the synchronized block boundaries (block start or block end).
编译器可以在同步块中重新排序指令。并且编译器可以*地重新排序指令(只要它们保持之前)或之后(只要它们保持在之后)同步块。但是,编译器无法在同步块边界(块开始或块结束)上重新排序指令。
Thus, the construction and assignment which are wholly within the synchronized block can be reordered, and an outside viewer which has not correctly synchronized can see the assignment before the construction.
因此,完全在同步块内的构造和分配可以被重新排序,并且未正确同步的外部观察者可以在构造之前看到分配。
#2
0
First of all:
首先:
Again when using the synchronized keyword, the code reorder never happens as per the JMM specification.
再次使用synchronized关键字时,代码重新排序永远不会按照JMM规范发生。
The above statement is not fully accurate. The JMM defined the happens-before relationship. The JLS only defines the program order and happens-before order. See 17.4.5. Happens-before Order.
以上陈述并不完全准确。 JMM定义了之前发生的关系。 JLS仅定义程序顺序并在订单之前发生。见17.4.5。发生在订单之前。
It has effects on the reordering of instructions. For example,
它对指令的重新排序有影响。例如,
int x = 1;
synch(obj) {
y = 2;
}
int z = 3;
Now for the above piece of code, the below types of reordering are possible.
现在对于上面的代码,可以进行以下类型的重新排序。
synch(obj) {
int x = 1;
y = 2;
int z = 3;
}
The above is a valid reordering.
以上是有效的重新排序。
See Roach Motels and The Java Memory Model.
请参阅Roach Motels和Java Memory Model。
synch(obj) {
int z = 3;
y = 2;
int x = 1;
}
The above is also a valid reordering.
以上也是有效的重新排序。
What is not possible is that y=2 will only be executed after the lock has been acquired and before the lock is released this is what guaranteed given by JMM. Also to see the correct effects from another thread, we need to access y inside the synchronized block only.
不可能的是,y = 2只会在获取锁定之后和锁定释放之前执行,这是JMM给出的保证。另外,为了从另一个线程看到正确的效果,我们只需要访问synchronized块内的y。
Now I come to DCL.
现在我来到DCL。
See the code of DCL.
请参阅DCL的代码。
if (singleton == null)
synch(obj) {
if(singleton == null) {
singleton == new Singleton()
}
}
return singleton;
Now the problem in the above approach is:
现在上述方法的问题是:
-
singleton = new Singleton()
is not a single instruction. But a set of instructions. It is quite possible that a singleton reference is assigned an object reference first, before fully initializing the constructor.singleton = new Singleton()不是单个指令。但是一套说明。在完全初始化构造函数之前,很可能首先为单例引用分配对象引用。
-
So if 1 happens then it's quite possible the other thread reads a singleton reference as a non null and thus is seeing a partially constructed object.
因此,如果1发生,则很可能另一个线程将单个引用读取为非null,因此看到部分构造的对象。
The above effects can be controlled by making a singleton as volatile which also establishes happens-before guarantees and visibility.
可以通过将单例设置为易失性来控制上述效果,这也可以建立先发生后的保证和可见性。
#3
0
Why is compiler reordering the sequence of constructor events when it is inside the synchronized() block?
为什么编译器在synchronized()块内部重新排序构造函数事件的序列?
It would typically do this to make the code run faster.
通常这样做可以使代码运行得更快。
The Java Language Specification (JLS) says that the implementation (for example, the compiler) is allowed to reorder instructions and sequences of instructions subject to certain constraints.
Java语言规范(JLS)表示允许实现(例如,编译器)重新排序受特定约束的指令和指令序列。
The problem is that the broken variants of DCL make assumptions that fall outside of what the JLS says can be made. The result is an execution that the JLS says is not well-formed. Whether this manifests itself as an actual bug / unexpected behaviour depends on the compiler version, the hardware and various other things.
问题在于DCL的破坏变体使得假设超出了JLS所说的范围。结果是JLS所说的执行不正确。这是否表现为实际的错误/意外行为取决于编译器版本,硬件和其他各种因素。
But the point is that the compiler isn't doing anything wrong. The fault is in the DCL code.
但重点是编译器没有做错任何事情。故障在DCL代码中。
I just want to add that the JIT compiler is often not reordering the events per se. what it is often doing is removing constraints on hardware-level memory read/write actions. For example, by removing the constraint that a particular memory write is flushed to main memory, you allow the hardware to defer (or even skip entirely) a slow write-to-memory, and just write to the L1 cache. By contrast, the end of a synchronized
block will force the cached writes to main memory, incurring extra memory traffic and (probably) a pipeline stalls.
我只想补充一点,JIT编译器通常不会重新排序事件本身。它经常做的是删除硬件级内存读/写操作的约束。例如,通过删除特定内存写入刷新到主内存的约束,您允许硬件推迟(甚至完全跳过)慢速写入内存,并只写入L1缓存。相比之下,同步块的结束将强制缓存写入主内存,导致额外的内存流量和(可能)管道停顿。