volatile keyword is a lightweight synchronization mechanism that guarantees memory visibility and ordering for shared variables.
In this article, we will explore memory visibility, understand the happens-before relationship, and learn when the
volatile keyword is sufficient—and when it is not.
What is volatile?
When a variable is declaredvolatile, Java guarantees that reads and writes to that variable go directly through main memory instead of allowing threads to rely only on local cached copies.
class SharedFlag {
volatile boolean running = true;
}
This means when one thread changes running, other threads can immediately observe the latest value.
Modern CPUs and the JVM use caches, registers, and optimizations for performance. Because of this, each thread may temporarily work with its own copy of shared data.
Without proper synchronization, one thread's update may not become visible to another thread immediately.
Without volatile
class Worker implements Runnable {
boolean running = true;
public void run() {
while (running) {
// keep working
}
System.out.println("Stopped");
}
}
Another thread does:
worker.running = false;
You may expect the loop to stop instantly, but it might continue forever because the worker thread may keep reading a cached value of true.
Using volatile
class Worker implements Runnable {
volatile boolean running = true;
public void run() {
while (running) {
// keep working
}
System.out.println("Stopped");
}
}
Now when another thread writes false, the worker thread sees the update reliably.
How volatile Works
Thevolatile keyword provides two major guarantees:
1. Visibility Guarantee: A write to a
volatile variable by one thread becomes immediately visible to all other threads reading that variable.
2. Ordering Guarantee: The Java Memory Model prevents certain instruction reordering around
volatile reads and writes.
As a result, operations performed before writing to a
volatile variable cannot be reordered after the write, and operations performed after reading a volatile variable cannot be reordered before the read.
These guarantees establish a happens-before relationship between threads.
For a
volatile variable, every write to that variable happens-before every subsequent read of the same variable.
Consequently, a thread reading the variable is guaranteed to observe the most recently written value along with all the changes made by the writing thread before the volatile write.
Example:
class Example {
int data = 0;
volatile boolean ready = false;
void writer() {
data = 42;
ready = true;
}
void reader() {
if (ready) {
System.out.println(data);
}
}
}
If thread A executes writer() and thread B later observes ready == true, then thread B is guaranteed to also observe data == 42.
In Java, a write to a volatile variable acts as a memory visibility point. All normal variable writes performed before the volatile write (such as
data = 42) become visible to any thread that subsequently reads the volatile variable.
In simple terms,
ready acts as a signal that also carries all earlier memory updates with it. Once another thread sees ready == true, it is guaranteed to see every change made before that volatile write.
When "volatile" is Enough?
Thevolatile keyword is a good choice when multiple threads need to share the latest value of a variable, but no thread performs compound operations on that variable.
It is commonly used for flags, configuration switches, and shutdown signals, where one thread updates the variable and many other threads simply read it.
volatile boolean shutdown = false;
The variable should also be updated using simple assignments rather than operations that depend on its current value. For example, assigning a new value is safe because it consists of a single write operation.
status = true;
When "volatile" is NOT Enough?
Thevolatile keyword guarantees only visibility and ordering. It does not make multiple operations atomic or prevent multiple threads from executing the same code simultaneously.
One common example is incrementing a counter.
volatile int count = 0;
count++;
Although count is volatile, the count++ operation is not atomic. It first reads the current value, then increments it, and finally writes the new value back.
If multiple threads perform these three steps at the same time, some updates may be lost. For such scenarios, use
AtomicInteger instead.
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
Similarly, volatile cannot guarantee consistency when multiple variables must be updated together.
If two or more variables represent a single logical state, they should be protected using synchronization so that all updates occur as one atomic operation.
int x;
int y;
The volatile keyword is also insufficient for check-then-act operations.
if (!initialized) {
initialize();
}
If two threads execute this code simultaneously, both may observe initialized as false and both may invoke initialize(). To avoid this race condition, the entire critical section must be synchronized.
Whenever a section of code performs multiple related operations that must execute exclusively, use
synchronized or the classes in the java.util.concurrent.locks package instead of volatile.
In most cases, a variable protected by a synchronized block does not need to be volatile.
When a thread enters a synchronized block, it acquires the monitor lock. When it exits the block, it releases the lock. The Java Memory Model guarantees that:
- Changes made inside the synchronized block are flushed to main memory when the lock is released.
- A thread acquiring the same lock later sees all those changes.
This means synchronized already provides:
1. Mutual exclusion
2. Memory visibility
3. Ordering (happens-before relationship)
Declaring a collection reference asIn the next article, we will dive deeper into the Java Memory Model and understand how threads interact with main memory, caches, and instruction reordering.volatiledoes not make the collection itself thread-safe.Thevolatile List<String> list;volatilekeyword guarantees only that all threads see the latest reference stored inlist. It does not make operations such asadd(),remove(), orclear()safe for concurrent access.
If multiple threads modify the collection simultaneously, you should use thread-safe collections or synchronize access explicitly.