To add to the answer above: total order of synchronization actions is also required for SC-DRF.
Sequential consistency for data-race-free programs (SC-DRF) is one of the key features of Java Memory Model.
From JLS 17.4.5:
If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent (§17.4.3).
This is an extremely strong guarantee for programmers. Programmers do not need to reason about reorderings ...
Sequential consistency (see JLS 17.4.3) means that there is a single total order over all program actions, the same for every thread.
But if synchronization actions weren't in totally ordered, then SC-DRF would be violated in some cases.
One of these cases would be famous IRIW:
volatile int x, y; // initially zero
Thread 1 | Thread 2 | Thread 3 | Thread 4
x = 1; | y = 1; | while(x != 1); | while(y != 1);
| | int r1 = y; | int r2 = x;
When x and y are volatile, then result (r1=0, r2=0) is impossible.
Let's replace volatile with setRelease+getAcquire — we only lose total order over synchronization actions.
From VarHandle:
In addition to obeying Acquire and Release properties, all Volatile operations are totally ordered with respect to each other.
int x, y; // initially zero, with VarHandles X and Y
Thread 1 | Thread 2 | Thread 3 | Thread 4
X.setRelease(this, 1); | Y.setRelease(this, 1); | while(X.getAcquire(this) != 1); | while(Y.getAcquire(this) != 1);
| | int r1 = Y.getAcquire(this); | int r2 = X.getAcquire(this);
Now result (r1=0, r2=0) is possible
=> Threads 3 and Threads 4 see writes to x and y in different order
=> violation of SC, which requires a single total order over all program actions, the same for every thread
So, SC-DRF requires total order of synchronization actions.
There is a similar example in C++ docs for memory_order.