The volatile keyword in Java
The volatile keyword is used in multithreaded Java programming.
It is used as a field modifier (alongside private, public etc)
to indicate that the field in question may be accessed by different Java threads.
For example, we can declare that a boolean flag is accessed by multiple threads:
private volatile boolean stopFlag = false;
Using volatile is one potential way of ensuring that your Java program
is thread-safe and will behave as expected when multiple threads access the
value in question. Java has various other language and platform features for dealing
with multithreading, including the synchronized keyword. However,
the volatile keyword is essentially lock-free and hence the most
lightweight means of providing thread-safety.
What volatile actually does
The purpose of volatile is to ensure that when one thread writes a value to the field,
the value written is "immediately available" to any thread that subsequently reads it.
Conversely, other threads reading the value are "forced" to take that immediately available value,
rather than relying on a value previously read and cached by those other threads. Without volatile
or any other form of synchronization, then modern CPU architecture and optimisations can lead to
unexpected results. A thread can work on a cached, out-of-date copy of the value in question, or not write it
back to main memory in time for it to be accessed by another thread as expected.
Under the hood, volatile causes reads and writes to be accompanied by a
special CPU instruction known as a memory barrier.
The memory barrier forces the required execution ordering and memory visibility between the different CPU cores,
so that to the programmer, reading and writing to the shared volatile field then behaves as expected.
You can think of volatile informally as being equivalent to automatically putting synchronized around each individual
read or write of the field in question. And synchronized
can be used to achieve a similar effect (also involving memory barriers under the hood). But synchronized is
a more "heavyweight" solution under the hood because it also entails lock management, whereas volatile
is lock-free.
When to use volatile
Given the above description, when is volatile actually used?
In practice, volatile is most suitable:
- when a thread writes the value of the volatile field, the value it writes does not depend on
the previous value just read;
- when you have a simple flag or other primitive item of data that is accessed
by multiple threads;
- when you have a more complex immutable object that is initialised by one
thread then accessed by others: here you want to update the reference to the
immutable data structure and allow other threads to reference it.
For more information, see: when to use volatile.
When is volatile not suitable?
The volatile keyword is not usually suitable when:
- the value you are writing depends on the value just read (for example, when incrementing a counter);
- when multiple values are being written to separate fields which must share a consistent state between
one another.
Why is volatile not sufficient for incrementing a counter? The problem is that incrementing involves
reading the current value, adding 1 to it, and then writing back the result. These three operations cannot
necessarily happen in a single CPU instruction. Therefore, even with a volatile field, another thread
could "sneak in" bewteen the read and the write operations. The result would be that we could miss an
increment. With the simple example of a counter, we might as a programmer decide to take the risk. We might decide that
the benefit of having a lock-free solution outweighs the consequence of missing an occasional increment.
But where there would be actual harm from leaving the data in an inconsistent state, volatile
would not be a suitable solution on its own.
volatile compared to synchronized etc
In view of our explanation of volatile so far, we can compare and contrast it to other
thread-safety mechanisms in Java. The table below summarises these similarities and differences. Some of these
differences affect what you can and cannot do functionally with volatile vs synchronized.
Others are largely differences in what happens "under the hood" that we will discuss in more detail in the following
sections. While not functional differences as such, these can still have knock-on consequences for the developer.
Characteristic | Synchronized | Volatile |
Type of variable | Object | Object or primitive |
Null allowed? | No | Yes |
Can block/deadlock? | Yes | No |
Suitable for maintaining consistency between multiple fields | Yes | No |
Suitable for read-update-write | Yes | Not usually. See above example of a counter, plus the separate section on the Atomic get-set of volatiles |
All cached variables synchronized on access? | Yes | Yes (since Java 5) |
When synchronization happens | When you explicitly enter/exit a synchronized block | Whenever a volatile variable is accessed. |
Differences between synchronized and volatile
In other words, the main differences between synchronized and
volatile are:
- a primitive variable may be declared volatile (whereas you can't
synchronize on a primitive with synchronized— you must always synchronize on an object);
- as mentioned above, an access to a volatile variable is lock-free and never has the potential to block:
we're only ever doing a simple read or write, so unlike a synchronized block we will never hold on to any lock;
- because accessing a volatile variable never holds a lock, it is not suitable
for cases where we want to read-update-write as an atomic operation
(unless we're prepared to "miss an update")— we mentioned this above in our example of
incrementing a counter;
- a volatile variable that is an object reference may be null (because you're
effectively synchronizing on the reference, not the actual object).
Attempting to synchronize on a null object will throw a NullPointerException.
Other details and examples of volatile
Strictly speaking, Java's definition of volatile
is a little tighter than we have mentioned so far. We hinted at one or two of these details in the table above.
On the next page, we take a more detailed look at a typical
example of using volatile.
Later, we return to topics such as:
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.