The JMM gives us certain guarantees about ordering, which we have to know/use in order to avoid data-races.
Note that the JMM itself builds upon the guarantees given by our processor architecture:

The JVM has to consider these constraints when compiling our code.
7.1 Program Order

Program Order (PO)
An order within a thread. It doesn’t guarantee memory access order across threads. Its main purpose is to link the execution trace back to the original source code.
JMM Intra-thread guarantee
The JMM guarantees us that intra-thread everything appears as if it was executed sequentially! (PO)
7.2 Synchronisation Actions

Synchronisation actions are a set of specific actions, as designated by the JMM, which guarantee the order of execution.
Synchronization Actions (SA)
Special actions that create ordering constraints. Includes reads/writes to volatile variables, lock/unlock operations, thread start/join, etc.
Synchronization Order (SO)
A global total order agreed upon by all threads for all synchronization actions.
The set of synchronization actions
- Volatile reads and volatile writes.
- Lock and unlock actions on a monitor (entering/exiting a synchronized block or method, or explicit Lock.lock() / unlock()).
- The action that starts a thread (Thread.start()) and the action that detects a thread has terminated (a successful Thread.join(), or isAlive() returning false).
- The initial action in a thread and the final action in a thread.
- The default initialization of an object’s fields (conceptually a write of default values that happens at construction).
- External actions — I/O interactions with the “outside world” that the JMM can’t see through.
LEMMA
Synchronisation actions themselves do not guarantee a specific ordering. Synchronizes with (SW) only pairs the specific actions which “see” each other
7.2.1 Volatile Keyword

Volatile is a keyword that ensures that a thread accessing the variable sees the most recent version of it.
When you read a volatile variable in a thread, all writes up to and including the last write to that volatile are now visible to your thread!
It’s basically a flag to the compiler to not optimise that field away as it’s shared.

In this example, marking x as volatile would fix the issue.
7.3 Happens-Before Closure

Example: In example, because volatile introduces a synchronisation order we get the following relationships:
x = 1<int r1 = y(PO)y = 1<int r2 = x(PO)
can never happen because:
- Assume
r1 = y<y = 1andr2 = x<x = 1 - Then because of PO we get:
which forms a cycle and thus a contradiction, as is impossible.
The possible outcomes are , and , depending on which ordering we choose. It’s non-deterministic.
7.3.1 Inconsistent orderings

Case 4 is not possible here:
- if
r1 = greadsg = 1, this means thatx = 1must have already executed (PO) - Thus we cannot read
r2 = xwherex = 0!
7.3.2 Transitive closure
We can see, if we pick a sw synchronisation order, we can derive the happens-before relationships from the transitive closure.

7.3.3 Data Races Allowed
JMM’s happens-before consistency rule (JLS §17.4.6) — the actual definition of which values a read is allowed to return.
Stated precisely: a read of variable may observe a write (to ) exactly when:
- does not happen-before (no reading from the future)
- there is no write (to ) with (no write strictly between and in hb)
Why “races are allowed”. A data race is defined as two conflicting accesses (one of them a write) on the same variable, hb-unordered.
- The rule above says a read may observe any hb-unordered write to its variable. So racy programs aren’t forbidden — they’re given defined, if non-deterministic, semantics.
This explains why for Dekker’s, we can observe :
int x = 0, y = 0; // plain
// Thread 1:
x = 1; int r1 = y;
// Thread 2:
y = 1; int r2 = x;because no read and write order exists, we may observe any unordered write!