问题
I'd like to clarify how happens-before relation works with volatile variables. Let we have the following variables:
public static int i, iDst, vDst;
public static volatile int v;
and thread A:
i = 1;
v = 2;
and thread B:
vDst = v;
iDst = i;
Are the following statements correct in accordance with Java memory model (JMM)? If not, what would be correct interpretation?
i = 1
always happens-beforev = 2
v = 2
happens-beforevDst = v
in JMM only if it's actually happens before in timei = 1
happens-beforeiDst = i
in JMM (andiDst
will be predictably assigned1
) ifv = 2
actually happens beforevDst = v
in time- Otherwise order between
i = 1
andiDst = i
is undefined and resulting value ofiDst
is undefined as well
Mistake in the logic:
There is no "wall clock time" concept in JMM, and we should rely on synchronization order as an ordering guide for v = 2
and vDst = v
. See the chosen answer for further details.
回答1:
i = 1
always happens-beforev = 2
True. By JLS section 17.4.5,
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
v = 2
happens-beforevDst = v
in JMM only if it's actually happens before in timei = 1
happens-beforeiDst = i
in JMM (andiDst
will be predictably assigned1
) ifv = 2
actually happens beforevDst = v
in time
False. The happens-before order does not make guarantees about things happening before each other in physical time. From the same section of the JLS,
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
It is, however, guaranteed that v = 2
happens-before vDst = v
and i = 1
happens-before iDst = i
if v = 2
comes before vDst = v
in the synchronization order, a total order over the synchronization actions of an execution that is often mistaken for the real-time order.
- Otherwise order between
i = 1
andiDst = i
is undefined and resulting value ofiDst
is undefined as well
This is the case if vDst = v
comes before v = 2
in the synchronization order, but actual time doesn't come into it.
回答2:
Yes all of them are correct according to this section about happens-before order:
i = 1
always happens-beforev = 2
since:
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
v = 2
happens-beforevDst = v
in JMM only if it's actually happens before in time, sincev
is volatile, and
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
i = 1
happens-beforeiDst = i
in JMM (andiDst
will be predictably assigned 1) ifv = 2
actually happens beforevDst = v
in time. This is because in this case:i = 1
happens-beforev = 2
v = 2
happens-beforevDst = v
vDst = v
happens-beforeiDst = i
If hb(x, y) and hb(y, z), then hb(x, z).
EDIT:
As argued by @user2357112, it seems statements 2 and 3 are not accurately correct. The happens-before relationship does not necessarily impose a timing order between actions having this relationship, as mentioned in the same section of the JLS:
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
Therefore, in terms of the rules mentioned in the JLS, we shouldn't make assumptions on the actual timing of the execution of the statements.
回答3:
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.
来源:https://stackoverflow.com/questions/30246007/java-memory-model-volatile-variables-and-happens-before