What is proper reordering for Java synchronization?

Many people asked similar questions like this, but none of their answers satisfied me. Only two reordering rules, which I am very sure, are as follows:

  • Operations inside a synchronized block (or just call its critical section) are allowed to be reordered if it confirms the semantics of as-if-serial.
  • Operations (including both reading and writing) are prohibited from moving (reordering) from the critical section.

However, for those operations that are before or after the synchronized block, can they be moved to the critical section? For this problem, I found the opposite. For example, cookbook said that the compiler inserts some barriers after MonitorEnter and before MonitorExit:

MonitorEnter (any other needed instructions go here ) [LoadLoad] <===MB1:Inserted memory barrier [LoadStore] <===MB2:Inserted memory barrier (Begin of critical section) .... (end of critical section) [LoadStore] <===MB3:Inserted memory barrier [StoreStore] <===MB4:Inserted memory barrier (any other needed instructions go here ) MonitorExit 

According to the layout made by the compiler and the below pseudo-code:

  Load a; Load b; Store 1; Store 2; MonitorEnter (any other needed instructions go here ) [LoadLoad] <===MB1 [LoadStore] <===MB2 (Begin of critical section) .... (end of critical section) [LoadStore] <===MB3 [StoreStore] <===MB4 (any other needed instructions go here ) MonitorExit Store 3; Store 4; Load c; Load d; 

In accordance with the cookbook rules and reordering rules, which are followed by such XY memory restrictions (X - Load or Store, Y - Load or Store), it seems to me that a valid / invalid reordering looks like this:

Understanding 1: Any stores (Store 3 and Store 4 here) after MonitorExit can NOT be moved to MB3 and MB4 due to the existence of LoadStore (MB3), followed by StoreStore (MB4). That stores after MonitorExit can not be transferred to the critical section. However, it can be moved after MB4, namely the bracket area.

Understanding 2: Any loads (Load a and Load b here) before MonitorEnter CANNOT be moved down after MB2 and MB1 due to the existence of LoadLoad (MB1), followed by LoadLoad (MB2). load before MonitorEnter cannot be transferred at a critical moment. However, it can be moved down after MB2, namely in the area of ​​brackets.

Understanding 3: Any loads (Load c and Load d here) after MonitorExit can be moved to MonitorExit, including the critical section and bracket area, but it cannot exceed MonitorEnter.

Understanding 4: Any stores (Store 1 and Store 2 here) before MonitorEnter can be moved down after MonitorEnter, including the critical section and bracket area, but it cannot exceed MonitorExit.

However, all of the above understanding or claim contradicts what Jeremy Manson said on the blog , where he claimed to give the code below:

 x = 1;//Store synchronized(o) { z = z + 1; } y = 1//Store 

The following code release order changes are allowed:

 synchronized(o){ y = 1;//I added this comment:Store moved inside the critical section z = z + 1; x = 1;//I added this comment:Store moved inside the critical section } 

According to the understanding of 1, "y = 1" cannot move inside the critical section, so I was just confused, which one is correct and complete?

+5
source share
1 answer

Reordering does not care about memory barriers. Even if the compiler always inserts the strongest memory barrier between any two instructions, this reordering is still allowed independently.

Now, given the sequence of instructions, perhaps after reordering from the original sequence, the compiler needs to insert proper memory barriers between some instructions.

For example, given the original sequence of commands

 volatile store x normal store y 

It does not need memory protection between two instructions.

However, the compiler can change it to

 normal store y volatile store x 

then a StoreStore barrier is needed between the two instructions. The CPU has only the instruction "store", has no idea about normal / unstable storage. And the CPU can have stores outside stores. Java semantics requires that another processor must not observe the store x effect before the volatile store y effect; therefore, StoreStore is used to force the CPUs to keep them in order.

(If the compiler is smart enough, it remembers that the original program does not require ordering y->x , so this barrier is not really needed. But let the compiler not be so smart.)


Roach Bridge Model -

The point of the JMM is to set some (partial) orders among the instructions for the various threads so that the read / write effects can be determined. In the following example

 thread 1 thread 2 a1 a2 | }b1 -----> b2{ | c1 c2 

the synchronization order is set b1->b2 , which can be volatile store -> volatile load or monitor exit -> monitor enter . This binds a1->b1->b2->c2 in order-up order.

Since we need to guarantee the order of a1->c2 , a1 should not be reordered with b1 , and c2 should not be reordered with b2 ; those. roach cannot "check."

On the other hand, JMM wants to be as weak as possible; he says nothing about the effects between c1 and a2,b2,c2 ; therefore, c1 can be freely reordered using b1 . Similarly, a2 can be reordered with b2 . That is, roach can "register".

0
source

Source: https://habr.com/ru/post/1242974/


All Articles