JMM specification example

96 Views Asked by At

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:

  1. 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 x and y are actions of the same thread and x comes before y in program order, then hb(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...

  1. 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.
2

There are 2 best solutions below

0
pveentjer On

Does volatile in this example really ensure that we will observe sequential consistency results and why?"

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.

I saw another interesting detail. Jmm authors write : If x and y are actions of the same thread and x comes before y in program order, then hb(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.

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)

int A,B=0

Thread 1; 
B = 1;    (a)
r2 = A;   (b)

Thread 2;
A = 2;    (c)
r1 = B;   (d)

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:

1: r1 = B;   (d)
2: r2 = A;   (b)
3: B = 1;    (a)
4: A = 2;    (c)

Even though program order is clearly violated.

0
Quân Anh Mai On

You are misunderstanding the JMM, it is a formalism of the requirements on the JVM to ensure certain behaviours in multithreading environments. As a result, all definitions in the JMM are pure mathematical constructs, no more and no less. These mathematical constructs are then used to formally defined the behaviours of the program, but they do not in anyway impose any restriction on the actual execution of the program, as long as its behaviours are consistent with what are defined.

As for happen-before relationship, it is a somewhat misleading name, but if you think it in a pure mathematical sense then it is clear what does it do. In fact, a happens before b in the JMM sense does not mean that they happen in that order in the behavioural sense, and any thread (including the one that executes those operations) are permitted to observe b before a when there is no other restrictions. The opposite is also true, just because a is observed to be executed before b (e.g a read that observes a write from another thread) does not mean between them there is a happen-before relationship from the perspective of the JMM.