Volatile Keyword in Java
The volatile keyword in Java marks a field whose value may be changed by one thread and read by others. It guarantees visibility: every write to a volatile field is seen by every later read from any thread. Without it, a thread may keep using an old copy of a value for a long time, because the JIT compiler and the CPU are allowed to cache and reorder plain reads and writes. However, volatile does not make compound actions like count++ atomic. In this lesson, you will see the visibility problem happen and learn exactly what volatile does and doesn't do.
Syntax
private volatile boolean running = true;The Visibility Problem
Each thread may see shared memory through caches and optimizations. When a field isn't volatile (or protected by a lock), the JIT compiler is allowed to read it once and reuse the value, for example turning while (!stop) { } into an endless loop that never checks again.
Example: A Loop That Never Sees the Change
Let's see an example with two identical loops, one reading a plain field and one reading a volatile field. The main thread sets both flags after half a second. The threads are daemons, so the program can end even if a loop never stops.
public class Visibility {
static boolean plainFlag = false;
static volatile boolean volatileFlag = false;
public static void main(String[] args) throws InterruptedException {
Thread plain = new Thread(() -> {
long spins = 0;
while (!plainFlag) {
spins++;
}
});
Thread vol = new Thread(() -> {
long spins = 0;
while (!volatileFlag) {
spins++;
}
});
plain.setDaemon(true);
vol.setDaemon(true);
plain.start();
vol.start();
Thread.sleep(500);
plainFlag = true;
volatileFlag = true;
plain.join(1000);
vol.join(1000);
System.out.println("Plain loop stopped? " + !plain.isAlive());
System.out.println("Volatile loop stopped? " + !vol.isAlive());
}
}Plain loop stopped? false
Volatile loop stopped? trueExplanation: After the plain loop ran for a while, the JIT compiler optimized it, reading plainFlag only once; the change made by the main thread was never seen, so that loop spins forever. The volatile read must see the latest write, so that loop stops. This result appeared on every run on the test machine, but because it depends on JIT optimization, a plain loop might stop on another JVM, which is exactly why such bugs are dangerous.
What volatile Does and Doesn't Do
volatile guarantees | volatile does not guarantee |
|---|---|
| Visibility: reads see the latest write | Atomicity of count++ or check-then-act |
| No reordering of the volatile access with earlier writes (happens-before) | Mutual exclusion between threads |
Atomic reads and writes of long and double | Thread safety of the object a reference points to |
Example: volatile Is Not Enough for count++
import java.util.concurrent.atomic.AtomicInteger;
public class NotAtomic {
static volatile int volatileCount = 0;
static final AtomicInteger atomicCount = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Runnable work = () -> {
for (int i = 0; i < 100_000; i++) {
volatileCount++;
atomicCount.incrementAndGet();
}
};
Thread a = new Thread(work);
Thread b = new Thread(work);
a.start();
b.start();
a.join();
b.join();
System.out.println("volatile reached 200000? "
+ (volatileCount == 200_000));
System.out.println("atomic reached 200000? "
+ (atomicCount.get() == 200_000));
}
}volatile reached 200000? false
atomic reached 200000? trueExplanation: volatileCount++ is still a read, an add and a write; another thread can slip in between, so updates are lost even though every write is visible. AtomicInteger.incrementAndGet() performs the whole update as one atomic step.
Common Mistakes
Using volatile for counters. count++ still loses updates.
Reading a shared stop flag without volatile or a lock. The loop may never stop.
Making a reference volatile and assuming the object is thread-safe. Only the reference is covered.
Interview Corner
What does the volatile keyword do?
volatile marks a field so that every write to it is visible to subsequent reads by other threads and the compiler and CPU may not reorder accesses around it in ways that break this; formally, a write to a volatile field happens-before every later read of that field. It also makes reads and writes of long and double atomic. It doesn't provide mutual exclusion or make compound actions such as count++ atomic, so it's suited to flags and published references, while counters need atomics or locks.
Practice
- Easy: Write a worker thread that loops until a
volatile boolean runningis set tofalseby the main thread after one second. - Harder: Remove
volatilefrom your flag, run the program several times, and explain what you observe.
Key Takeaways
Was this tutorial helpful?