Java
volatile keyword
concurrency
multithreading
Java programming

Simplest and understandable example of volatile keyword in Java

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The simplest way to understand volatile in Java is to think of it as a visibility guarantee between threads. When one thread changes a volatile variable, other threads are required to see the updated value instead of continuing to use a stale cached copy.

The Classic Stop-Flag Example

Here is the most common example:

java
1public class VolatileDemo {
2    private static volatile boolean running = true;
3
4    public static void main(String[] args) throws InterruptedException {
5        Thread worker = new Thread(() -> {
6            while (running) {
7                // do some work
8            }
9            System.out.println("Worker stopped.");
10        });
11
12        worker.start();
13
14        Thread.sleep(1000);
15        running = false;
16        worker.join();
17    }
18}

Without volatile, the worker thread might keep looping because it does not reliably observe that running changed to false.

With volatile, the change made by the main thread becomes visible to the worker.

What volatile Guarantees

For a variable marked volatile, Java guarantees:

  • reads see the most recently written value
  • writes are visible across threads
  • certain reordering around that variable is constrained

That makes volatile a good fit for:

  • stop flags
  • ready flags
  • state indicators where one write is read by other threads

It is about visibility, not about turning all thread interactions into safe atomic transactions.

What volatile Does Not Guarantee

This is the part people miss. volatile does not make compound actions atomic.

For example:

java
1public class CounterDemo {
2    private static volatile int counter = 0;
3
4    public static void main(String[] args) throws InterruptedException {
5        Thread t1 = new Thread(() -> {
6            for (int i = 0; i < 10000; i++) {
7                counter++;
8            }
9        });
10
11        Thread t2 = new Thread(() -> {
12            for (int i = 0; i < 10000; i++) {
13                counter++;
14            }
15        });
16
17        t1.start();
18        t2.start();
19        t1.join();
20        t2.join();
21
22        System.out.println(counter);
23    }
24}

You might expect 20000, but counter++ is not a single atomic action. It is a read, modify, write sequence, and two threads can still interfere with each other.

For atomic increments, use AtomicInteger or synchronization.

When to Use AtomicInteger Instead

java
1import java.util.concurrent.atomic.AtomicInteger;
2
3public class AtomicDemo {
4    private static final AtomicInteger counter = new AtomicInteger();
5
6    public static void main(String[] args) throws InterruptedException {
7        Thread t1 = new Thread(() -> {
8            for (int i = 0; i < 10000; i++) {
9                counter.incrementAndGet();
10            }
11        });
12
13        Thread t2 = new Thread(() -> {
14            for (int i = 0; i < 10000; i++) {
15                counter.incrementAndGet();
16            }
17        });
18
19        t1.start();
20        t2.start();
21        t1.join();
22        t2.join();
23
24        System.out.println(counter.get());
25    }
26}

This is the right tool when the operation itself must be atomic, not just visible.

Common Pitfalls

The most common mistake is believing volatile makes counter++ thread-safe. It does not. It only ensures that threads see updated values, not that compound updates happen safely.

Another issue is using volatile where multiple variables must stay consistent with each other. Visibility of a single field is not enough if correctness depends on coordinated updates across several fields.

A third pitfall is avoiding synchronization entirely just because volatile seems lighter. Sometimes you really need a lock or an atomic class because correctness matters more than minimal syntax.

Finally, do not use volatile as a magic "thread-safe" label. It solves a very specific visibility problem and should be chosen for that reason.

Summary

  • 'volatile in Java guarantees visibility of writes across threads.'
  • The simplest example is a shared stop flag read by one thread and written by another.
  • 'volatile does not make compound operations such as ++ atomic.'
  • Use AtomicInteger or synchronization when an update itself must be thread-safe.
  • Think of volatile as a visibility tool, not a full concurrency solution.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.