In my case, I want to implement the Acquire/Release
model in java8 with volatile
.
So I write the code that uses a volatile shared variable I
to guarantee the modifying of MAP
could be seen by other threads.
public static volatile int I = 0;
public static final Map<String, String> MAP = new HashMap<>();
// run at Thread-1
public static void write(){
MAP.put("test", "test");
I++; // release
}
// run at Thead-2
public static void read(){
int i = I; // acquire
MAP.get("test"); // want to see the modifying by write()
}
My question is that:
i
so that the acquire
operation is invalid?First note that ++
on a volatile
variable is not atomic, hence, you can’t rely on its value in case of multiple updates.
As long as there is only a single update, it may be sufficient to check whether the update did happen, but it is crucial to perform the check. Otherwise, there is no guaranty that the (supposed to be acquire) volatile read is subsequent to the (supposed to be release) volatile update.
Just consider the following timing:
Thread 1 Thread 2
┌ ┐ [ Read I ]
│ map update │ ┌ ┐
└ ┘ │ map query │
[ Write I ] └ ┘
Here, the two threads use the map concurrently, which is hopelessly broken, while the acquire and release actions have no consequences, as the acquire is not subsequent to the release.
You can only rely on this relationship if you check the value you’ve read and proceed only when it is the expected value written by the other thread.
Since the whole construct would work only for a single update, you can use a boolean
instead:
private static volatile boolean I = false;
private static final Map<String, String> MAP = new HashMap<>();
// run at Thread-1
public static void write(){
MAP.put("test", "test");
I = true; // release
}
// run at Thead-2
public static void read(){
if(I) { // acquire
MAP.get("test"); // want to see the modifying by write()
}
}
You can not use this for more than one update, as the thread wanting to perform a second update must ensure not to start updating the map before all threads reading the map after the first update have completed. But this information is not available at all with this approach.
In my opinion, you confuse terminology. volatile
in java offers sequential-consistency, while release/acquire
semantics do not. You can think about it as volatile
being stronger than release/acquire
. You can read the beginning of this answer, to understand what a potential difference between the two is.
Then, there is this subsequent word in the documentation and it's meaning:
A write to a volatile field happens-before every subsequent read of that same field.
What this means is that a ThreadA
has to observe that write to the volatile
field that ThreadB
did. That means you need to check if the write was seen:
boolean flag = ...
ThreadA: flag = true; // this is the "release"
ThreadB: if(flag) { .... } // this is the "acquire"
Only when ThreadB
enters the if statement
(that is when the acquire happened), can you guarantee that everything that happened before the write (flag = true
) will be visible in the if
clause.
You can achieve the same thing, via a cheaper way with VarHandle::setRelease
and VarHandle::getAcquire
, but be careful, as this offers less guarantees as volatile
, specifically it does not offer sequential consistency.
So your example is a bit flawed. You can simplify slightly your understanding of release/acquire
by thinking that release is a write that a reader must observe, with special rules, like volatile
or VarHandle::setRelease/getAcquire
. You do not do neither of these. Your I++
(besides not being atomic) is not observed by anyone else, your int i = I
is not checked to see if the value was really written.
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