I use volatile fields when said field is ONLY UPDATED by its owner thread and the value is only read by other threads, you can think of it as a publish/subscribe scenario where there are many observers but only one publisher. However if those observers must perform some logic based on the value of the field and then push back a new value then I go with Atomic* vars or locks or synchronized blocks, whatever suits me best. In many concurrent scenarios it boils down to get the value, compare it with another one and update if necessary, hence the compareAndSet and getAndSet methods present in the Atomic* classes.
Check the JavaDocs of the java.util.concurrent.atomic package for a list of Atomic classes and an excellent explanation of how they work (just learned that they are lock-free, so they have an advantage over locks or synchronized blocks)
They are just totally different. Consider this example of a volatile
integer:
volatile int i = 0;
void incIBy5() {
i += 5;
}
If two threads call the function concurrently, i
might be 5 afterwards, since the compiled code will be somewhat similar to this (except you cannot synchronize on int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
If a variable is volatile, every atomic access to it is synchronized, but it is not always obvious what actually qualifies as an atomic access. With an Atomic*
object, it is guaranteed that every method is "atomic".
Thus, if you use an AtomicInteger
and getAndAdd(int delta)
, you can be sure that the result will be 10
. In the same way, if two threads both negate a boolean
variable concurrently, with an AtomicBoolean
you can be sure it has the original value afterwards, with a volatile boolean
, you can't.
So whenever you have more than one thread modifying a field, you need to make it atomic or use explicit synchronization.
The purpose of volatile
is a different one. Consider this example
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
If you have a thread running loop()
and another thread calling stop()
, you might run into an infinite loop if you omit volatile
, since the first thread might cache the value of stop. Here, the volatile
serves as a hint to the compiler to be a bit more careful with optimizations.
You can't do compareAndSet
, getAndSet
as atomic operation with volatile boolean (unless of course you synchronize it).
AtomicBoolean
has methods that perform their compound operations atomically and without having to use a synchronized
block. On the other hand, volatile boolean
can only perform compound operations if done so within a synchronized
block.
The memory effects of reading/writing to volatile boolean
are identical to the get
and set
methods of AtomicBoolean
respectively.
For example the compareAndSet
method will atomically perform the following (without a synchronized
block):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Hence, the compareAndSet
method will let you write code that is guaranteed to execute only once, even when called from multiple threads. For example:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Is guaranteed to only notify the listener once (assuming no other thread sets the AtomicBoolean
back to false
again after it being set to true
).
volatile
keyword guarantees happens-before relationship among threads sharing that variable. It doesn't guarantee you that 2 or more threads won't interrupt each other while accessing that boolean variable.
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