The volatile
keyword is a critical part of Java’s memory model, especially in multi-threaded programming. Let me explain what it does, when to use it, and its key limitations.
🚀 What is volatile
?
volatile
is a keyword that can be applied to a variable (field) in Java to ensure visibility of changes across threads. It tells the JVM that:
✅ Every read of this variable must fetch the latest value directly from main memory (RAM).
✅ Every write to this variable must immediately flush to main memory.
🔥 Why is this needed?
In Java, to improve performance, each thread caches variables in its own thread-local cache (CPU registers, L1/L2 cache, etc.) instead of reading them from main memory every time. This can lead to stale data in multi-threaded programs.
Example (Without volatile
):
private boolean running = true; // shared variable between threads
public void stop() {
running = false;
}
public void run() {
while (running) {
// do some work
}
}
One thread calls stop()
, setting running = false
.
Another thread is running the while (running)
loop.
That second thread might never see the updated running
value, because running
might be cached in that thread’s local cache.
This is called the visibility problem.
✅ Solution: Use volatile
private volatile boolean running = true; // Now changes are visible to all threads
- Now, any change to
running
is immediately visible to all threads. - Every thread reads the fresh value directly from main memory.
💡 Key Point: volatile
only solves visibility, not atomicity.
🔥 Visibility vs Atomicity — Important!
Property | What it means | Solved by volatile ? |
---|---|---|
Visibility | Changes made by one thread are visible to others immediately. | ✅ Yes |
Atomicity | Compound actions (like count++ ) happen as a single indivisible step. | ❌ No |
🚨 Example where volatile
is NOT enough
private volatile int count = 0;
public void increment() {
count++; // not atomic!
}
count++
looks like one operation, but it’s actually three steps:- Read
count
. - Increment the value.
- Write back
count
.
- Read
- If two threads do this at the same time, they could both read 0, increment to 1, and write 1 back — losing one increment. This is called a race condition.
- To fix this, you need synchronization (like
synchronized
block) or ajava.util.concurrent.atomic
class (AtomicInteger
).
✅ When to use volatile
Case | Use volatile ? |
---|---|
Simple flag that is written by one thread and read by others (like running ) | ✅ Yes |
Counter that is updated by multiple threads | ❌ No, use AtomicInteger or synchronized |
Compound operations (check-then-act) | ❌ No, use locks |
🛠️ Example Use Case (Good)
private volatile boolean shutdown = false;
public void run() {
while (!shutdown) {
// do work
}
}
public void stop() {
shutdown = true; // no lock needed, all threads see this change
}
❌ Example Use Case (Bad)
private volatile int count = 0;
public void increment() {
count++; // not atomic, race condition!
}
This needs:
private final AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
🔔 Quick Summary Table
Keyword | Visibility | Atomicity | Use Case |
---|---|---|---|
volatile | ✅ Yes | ❌ No | Simple flags, stop signals |
synchronized | ✅ Yes | ✅ Yes | Full mutual exclusion |
AtomicInteger | ✅ Yes | ✅ Yes | Counters, accumulators |
💬 Final Tip
Think of
volatile
as a lighter, cheaper way to ensure visibility, but only for single variable reads/writes. For more complex operations (like increments or multiple variables needing consistent state), you need real locks.
✅ Example 1 — Correct Use of volatile
Scenario: Thread stop flag
- One thread is running a loop.
- Another thread can stop it.
class Worker extends Thread {
private volatile boolean running = true; // <-- volatile ensures visibility
public void run() {
while (running) {
System.out.println("Working...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Stopped");
}
public void stopWorker() {
running = false; // immediately visible to the running thread
}
}
public class VolatileExample {
public static void main(String[] args) throws InterruptedException {
Worker worker = new Worker();
worker.start();
Thread.sleep(2000);
worker.stopWorker(); // without volatile, worker might never see this change
}
}
❌ Example 2 — Wrong Use of volatile
Scenario: Increment counter
class Counter {
private volatile int count = 0;
public void increment() {
count++; // This is NOT atomic, even with volatile!
}
}
What’s wrong?
count++
is a read-modify-write operation.- Two threads could read the same value, both increment, and both write back — losing one update.
✅ Correct approach:
class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
}
🔥 volatile
vs synchronized
Feature | volatile | synchronized |
---|---|---|
Visibility | ✅ Guarantees visibility | ✅ Guarantees visibility |
Atomicity | ❌ No atomicity | ✅ Full atomicity |
Scope | Only for single variable reads/writes | Can protect any block of code |
Performance | Fast — just a memory fence | Slower — full lock acquisition |
Use Case | Simple flags (like stop signals) | Critical sections (like increments, check-then-act logic) |