What is the difference between getVolatile
vs getAcquire when using e.g. AtomicInteger?
PS: those are related to
The source of a synchronizes-with edge is called a release, and the destination is called an acquire.
from https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.3
It all comes back to how we want the optimisation of our code. Optimisation in terms of reordering code. Compiler might reorder to optimise. getAquire
ensures that the instructions following it will not be executed before it. These instructions might be reordered but they will always be executed after the getAquire
.
This works in combination with setRelease
(for VarHandle
) where setRelease
ensures that what happens before it is not reordered to happen after it.
Example:
Thread1:
var x = 1;
var y = 2;
var z = 3;
A.setRelease(this, 10)
assignments of x, y and z will happen before A.setRelease but might be reordered themselves.
Thread 2:
if (A.getAquire(this) == 10) {
// we know that x is 1, y is 2 and z = 3
}
This is a nice use case for concurrent program where you don't have to push volatility on everything but just need some instructions to be executed before another.
For getVolatile
, the variable is treated just like any volatile variable in Java. No reordering or optimisation is happening.
This video is a nice to see to understand whats called the "memory ordering modes" being plain, opaque, release/acquire and volatile.
One of the key differences between acquire/release and volatile (sequential consistency) can be demonstrated using Dekker's algorithm.
public void lock(int t) {
int other = 1-t;
flag[t]=1
while (flag[other] == 1) {
if (turn == other) {
flag[t]=0;
while (turn == other);
flag[t]=1
}
}
}
public void unlock(int t) {
turn = 1-t;
flag[t]=0
}
So lets assume that the writing of the flag is done using a release store and the loading of the flag is done using an acquire-load, then we'll get the following ordering guarantees:
.. other loads/stores
[StoreStore][LoadStore]
flag[t]=1 // release-store
flag[other] // acquire-load
[LoadLoad][LoadStore]
.. other loads/stores
The problem is that the earlier write to flag[t] can be reordered with the later load of flag[other] and the consequence is that 2 threads could end up in the critical section.
The reason the earlier store and the later load to a different address can be reordered is 2 fold:
To prevent this from happening a stronger memory model is needed. In this case we need sequential consistency since we do not want any reordering to take place. This can be realized by adding a [StoreLoad] between the store and the load.
.. other loads/stores
[StoreStore][LoadStore]
flag[t]=1 // release-store
[StoreLoad]
flag[other] // acquire-load
[LoadLoad][LoadStore]
.. other loads/stores
It depends on the ISA on which side this is done; e.g. on the X86 this is typically done on the writing side. E.g. using an MFENCE (there are other like XCHG that has an implicit lock or using a LOCK ADDL 0 to the stack pointer like the JVM typically does).
On the ARM this is done on the reading side. Instead of using a weaker load like an LDAPR, a LDAR is needed which will lead the LDAR to wait till the STLR's have been drained from the store buffer.
For a good read, check the following link: https://shipilev.net/blog/2014/on-the-fence-with-dependencies/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With