I've read 17.4.5 article of Jmm spec and I have a question about this example.
Thread 1; Thread 2;
B = 1; A = 2;
r2 = A; r1 = B;
Authors write that r2 and r1 can be equal to 0 because of reordering and visibility, I understand,
we have data race here because these memory accesses are not synchronized. And I've also seen how to prevent it. We can make A and B volatile(to prevent all data races in program) and be sure that we will observe only sequential consistent results([1,0], [0,1], [1,1]). But I can't understand why it is true, because I suppose that compiler or cpu can make read of r2 to happen before write of B, and r1 could happen before write of A. Volatile prevents reordering of instructions before write to such variable to happen after write and after read to happen before read. Explain me please:
- Does volatile is this example really ensure that we will observe sequential consistency results and why?
I saw another interesting detail. Jmm authors write:
If
xandyare actions of the same thread andxcomes beforeyin program order, thenhb(x, y).
I thought happens-before relationship happens between instructions in different threads, and it means that every read sees previous write and operations can't be reordered. But CPU and compiler can reorder these actions in a single thread and y can happen before x.
I know the following rules:
An unlock on a monitor happens-before every subsequent lock on that monitor.
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
and others...
- How it is possible for x to happen before y in hb order? I know that compiler is allowed to reorder instructions until the result of a program is consistent with program order and I can say next: program-order(x is before y), but I am not sure that x happens before y in happens-before order.
Yes; volatile is sufficient to ensure only sequential consistency executions.
Why? If A and B are made volatile, then there are no data races. And when there are no data races, there can only be sequential consistent executions. And in any sequential consistent execution, program order needs to be preserved (nobody should be able to proof it was not preserved because memory models are all about pretending).
At the implementation level the appropriate compiler and CPU memory fences will be added. How this is realized is an implementation details and keep in mind that fences are not a suitable replacement for the the JMM.
Only when there are no data races, the happens-before rules apply and you should get sequential consistent executions. But in the presence of data races, non sequential consistent executions are allowed. So if you have another look at the previous example (and A and B are plain variables)
There is a program-order happens-before edge between 'a' and 'b' due to program order. And the same goes for 'c' and 'd'. But because reading and writing of A,B are not synchronization operations, there are data races. And when there are data races, the above program order happens-before edges do not need to be preserved in any of the executions.
Therefor the following execution is allowed:
Even though program order is clearly violated.