volatile Keyword in Java – Visibility and Ordering Between Threads
The volatile keyword tells the JVM that a field is shared between threads, so every read must see the most recent write by any thread. It guarantees visibility and ordering, but not atomicity: count++ on a volatile field is still unsafe.
This tutorial explains the visibility problem volatile solves, the happens-before guarantee it provides, where it is the right tool (flags, safe publication, double-checked locking), and when you need synchronized or atomic classes instead.
1. The Visibility Problem
For speed, CPUs keep values in registers and caches, and the compiler and JIT may reorder or hoist instructions. Without synchronization, the Java Memory Model (JMM) does not guarantee that a value written by one thread is ever seen by another. A classic symptom is a loop that never stops:
static boolean running = true; // NOT volatile
// worker thread
while (running) { } // the JIT may read 'running' once and loop forever
// main thread
running = false; // the worker may never see this
2. volatile Fixes Visibility
Declaring the flag volatile forces every read to see the latest write, so the worker stops reliably:
public class VolatileFlag {
// Without volatile, the worker may never see the update and loop forever
private static volatile boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("Worker: started");
long iterations = 0;
while (running) {
iterations++; // busy work
}
System.out.println("Worker: saw running=false, stopping");
});
worker.start();
Thread.sleep(200);
System.out.println("Main: requesting stop");
running = false; // volatile write: visible to the worker
worker.join();
System.out.println("Main: worker finished");
}
}
Worker: started Main: requesting stop Worker: saw running=false, stopping Main: worker finished
A volatile stop flag (or status flag) written by one thread and read by others is the most common and safest use of volatile.
3. Ordering and Happens-Before
volatile also limits reordering. The JMM rule is: a write to a volatile variable happens-before every later read of that variable. Everything the writing thread did before the volatile write becomes visible to a thread that reads the new value. This lets a volatile flag "publish" ordinary data safely:
public class SafePublication {
private static int[] data; // plain field
private static volatile boolean ready; // volatile "guard"
public static void main(String[] args) throws InterruptedException {
Thread reader = new Thread(() -> {
while (!ready) Thread.onSpinWait(); // wait for the volatile flag
// Happens-before: everything written before 'ready = true' is visible here
System.out.println("Reader sees sum = " + (data[0] + data[1] + data[2]));
});
reader.start();
data = new int[] {10, 20, 30}; // 1. plain writes
ready = true; // 2. volatile write publishes them
reader.join();
}
}
Reader sees sum = 60
The array itself is not volatile, yet the reader is guaranteed to see {10, 20, 30}, because the plain writes come before the volatile write in program order.
4. volatile Is Not Atomic
count++ is three steps: read, add one, write. Two threads can read the same value and both write the same result, losing an update. volatile makes each read and write visible but does not make the three steps indivisible:
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileNotAtomic {
static volatile int volatileCount = 0;
static final AtomicInteger atomicCount = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 100_000; i++) {
volatileCount++; // read-modify-write: NOT atomic
atomicCount.incrementAndGet(); // atomic
}
};
Thread t1 = new Thread(task), t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Expected: 200000");
System.out.println("volatile int: " + volatileCount);
System.out.println("AtomicInteger: " + atomicCount.get());
}
}
Expected: 200000 volatile int: 195607 AtomicInteger: 200000
Use AtomicInteger/AtomicLong/LongAdder, or synchronized, for compound actions such as increments and check-then-act. One exception worth knowing: reads and writes of long and double are not guaranteed to be atomic unless they are volatile, so volatile does make a single 64-bit read or write atomic.
5. Double-Checked Locking
Lazily initialised singletons that avoid locking on every call need volatile. Without it, another thread could see a non-null reference to an object whose constructor has not finished:
public class LazySingleton {
static class Config {
private static volatile Config instance; // volatile is essential here
final String env;
private Config() {
env = "production";
System.out.println("Config created (constructor ran once)");
}
static Config getInstance() {
Config local = instance; // one volatile read on the fast path
if (local == null) {
synchronized (Config.class) {
local = instance;
if (local == null) {
instance = local = new Config();
}
}
}
return local;
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[4];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(Config::getInstance);
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
System.out.println("Same instance everywhere: " + (Config.getInstance() == Config.getInstance()));
System.out.println("env = " + Config.getInstance().env);
}
}
Config created (constructor ran once) Same instance everywhere: true env = production
The local variable saves a second volatile read on the fast path. For most singletons, a simpler alternative is the holder idiom (a static nested class holding the instance) or an enum.
6. volatile vs synchronized vs Atomic Classes
| Feature | volatile | synchronized | Atomic classes |
|---|---|---|---|
| Visibility | Yes | Yes | Yes |
| Atomic compound actions | No | Yes (whole block) | Yes (single variable) |
| Locking / blocking | No | Yes | No (lock-free CAS) |
| Applies to | Fields | Methods and blocks | Objects such as AtomicInteger |
| Typical use | Flags, published references | Multi-step updates of shared state | Counters, sequence numbers |
7. When to Use volatile
- Writes to the field do not depend on its current value (no
x++, no check-then-act), or only one thread ever writes it. - The field does not take part in an invariant with other fields.
- You need no locking while reading.
Good fits: stop/status flags, configuration references replaced as a whole, and safe publication of immutable objects. For anything more complex, prefer the higher-level java.util.concurrent tools.
8. Rules
volatileapplies only to fields (instance or static), not to methods, classes or local variables.- A field cannot be both
volatileandfinal; final fields already have their own visibility guarantee. - A volatile reference makes the reference visible, not the fields of the object it points to after later changes.
- Volatile reads and writes are slightly more expensive than plain ones, but much cheaper than locking.
9. Summary
- volatile guarantees that writes to a field are visible to all threads and limits reordering.
- A volatile write happens-before every subsequent read of the same field.
- volatile does not make compound actions like
count++atomic. - Use it for flags, safe publication and double-checked locking; use locks or atomics for compound updates.
Related Tutorials
Frequently Asked Questions (FAQ)
Q1. What is the volatile keyword in Java?
A field modifier that guarantees every thread sees the latest value written to that field, and prevents certain reorderings of reads and writes around it.
Q2. Does volatile make operations atomic?
Only single reads and writes. Compound actions like count++ or check-then-act are not atomic, so threads can still lose updates.
Q3. What is the difference between volatile and synchronized?
volatile provides visibility for a single field without locking. synchronized provides visibility and mutual exclusion, making a whole block atomic, but threads may block.
Q4. When should I use volatile?
For a field written independently of its current value (or by one thread only) and read by others, such as a stop flag or a reference to an immutable configuration object.
Q5. What is the happens-before relationship?
A JMM guarantee that the effects of one action are visible to another. A volatile write happens-before every later read of the same variable, so the reader also sees earlier writes by the writing thread.
Q6. Can a local variable be volatile?
No. Local variables are confined to one thread's stack and cannot be shared, so volatile is allowed only on fields.
Q7. Can a field be both volatile and final?
No. The compiler rejects the combination. A final field never changes after construction and already has safe-publication guarantees.
Q8. Why is volatile needed in double-checked locking?
Without it, the write of the reference may become visible before the constructor's writes, so another thread could use a partially constructed object.
Q9. Is volatile slower than a normal variable?
Slightly, because it inserts memory barriers and prevents some optimisations. It is still much cheaper than acquiring a lock.
Q10. Does volatile on an array make its elements volatile?
No. Only the array reference is volatile. For volatile-like element access, use AtomicIntegerArray, AtomicReferenceArray or VarHandle.
Q11. What is the difference between volatile and AtomicInteger?
Both provide visibility, but AtomicInteger also offers atomic compound operations such as incrementAndGet() and compareAndSet().
Q12. Are long and double reads atomic in Java?
Not necessarily for plain fields: the JMM allows a 64-bit value to be written in two halves. Declaring them volatile makes single reads and writes atomic.
Q13. Does volatile prevent caching in CPU caches?
Not literally. Modern CPUs keep caches coherent; volatile inserts memory barriers that stop the compiler and CPU from reordering or keeping values in registers, giving the JMM's visibility guarantees.
Q14. Can volatile replace synchronized?
Only when you need visibility of single, independent writes. If an update depends on the current value or several fields must change together, you need synchronized, locks or atomics.
Q15. What is the difference between volatile and transient?
volatile is about thread visibility. transient excludes a field from serialization. They are unrelated and can even be used together.
Q16. Does a volatile reference make the object's fields thread-safe?
No. It ensures threads see the latest reference and the object's state at publication. Later changes to the object's fields need their own synchronization, which is why immutable objects pair well with volatile references.
Q17. What happens without volatile on a stop flag?
The JIT may hoist the read out of the loop, so the worker thread never sees the change and runs forever. The bug often appears only in optimised, long-running code.
Q18. Can a method be volatile?
No. volatile applies only to fields.
Q19. What is the volatile keyword in C compared with Java?
In C/C++, volatile tells the compiler a value may change unexpectedly (such as hardware registers) and does not provide thread synchronization. Java's volatile has defined inter-thread visibility and ordering semantics.
Q20. What is VarHandle?
A Java 9+ API that offers volatile, acquire/release and compare-and-set access to fields and array elements, giving finer control than the volatile keyword alone.