All synchronization actions (volatile w/r, lock/unlock etc) form a total order. [1] That is a very strong statement; it makes analysis easier. For your volatile v
, either read is before write, or write is before read, in this total order. The order depends on the actual execution of course.
From that total order, we can establish partial orders happens-before. [2] If all reads and writes on a variable (volatile or not) are on a partial order chain, it's easy to analyze - a read sees the immediate preceding write. That is the main point of JMM - establishing orders on read/writes so they can be reasoned like a sequential execution.
But what if the volatile read is before the volatile write? We need another crucial constraint here - the read must not see the write. [3]
Therefore, we can reason that,
- read of
v
sees either 0 (init value) or 2 (volatile write)
- if it sees 2, it must be the case that the read is after the write; and in that case, we have
happens-before
chain.
Last point - read of i
must see one of the writes to i
; in this example, 0 or 1. It will never see a magic value not from any writes.
quoting java8 spec:
[1] http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.4
[2] http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5
[3] http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.7
random thoughts on the total order:
Because of this total order, we could say one sync action happens before another as if in time. That time may not correspond to wall clock, but it's not a bad mental model for our understanding. (In reality, one action in java corresponds to a storm of hardware activities, it is impossible to define a point in time for it)
And even physical time is not absolute. Remember that light travels 30cm in 1ns; on today's CPUs, temporal order is definitely relative. The total order actually requires that there is causality from one action to the next. That is a very strong requirement, and you bet that JVM tries hard to optimize it.