Question

Java volatile Keyword: Visibility, Uses, and Examples

javamultithreadingkeywordvolatile

Question

While working with Java code, I encountered the volatile keyword and am not familiar with it. What is volatile used for in Java? In which situations is it appropriate to use it correctly, and when should another concurrency tool be used instead?

Short Answer

By the end of this page, you will understand how volatile makes updates to a field visible across threads, why it is useful for simple state flags, and why it does not make operations such as count++ thread-safe.

Concept

volatile is a Java field modifier used in concurrent programs: programs where multiple threads run at the same time.

When one thread changes an ordinary field, another thread is not guaranteed to notice that change immediately. Threads may temporarily read cached or reordered values for performance. Declaring a field volatile creates a visibility guarantee:

  • A write to a volatile field by one thread becomes visible to other threads that subsequently read that field.
  • Reads and writes of the individual volatile field are atomic.
  • A write to a volatile field also makes earlier writes by that thread visible to a thread that reads the updated volatile value.

This makes volatile a good fit for simple shared state, especially a flag that one thread sets and another thread checks.

However, volatile does not turn a sequence of operations into one indivisible operation. For example, count++ means read, add one, then write. Two threads can interleave those steps and lose an update, even when count is volatile.

Use volatile for visibility. Use synchronized, locks, or atomic classes when you need to coordinate or atomically update shared state.

Mental Model

Imagine each thread has a desk with a copy of a shared notice board.

Without volatile, a worker may keep looking at an older copy on their desk. A manager can change running to false on the main board, but the worker is not guaranteed to see the new value promptly.

A volatile field is like a notice that must be checked against the shared board. When the manager updates it, workers who check it can see the latest update.

It does not make a multi-step task safe. If two workers both read “10”, each calculate “11”, and each write “11”, one increment is still lost. For that, you need a turn-taking mechanism such as synchronized or AtomicInteger.

Take Quiz

Syntax and Examples

Declare volatile on an instance or static field:

private volatile boolean running = true;

A common example is stopping a worker thread:

public class Worker implements Runnable {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            System.out.println("Working...");
        }

        System.out.println("Worker stopped.");
    }
}

One thread runs run(). Another thread calls stop(). Because running is volatile, the loop can observe the false value and finish.

volatile can also safely publish data when the volatile field is written after the data is prepared:

public class ResultHolder {
    private String result;
    private volatile boolean ready;

    public void produce() {
        result = "Finished";
        ready = true;
    }

    public String consumeWhenReady() {
        if (ready) {
            return result;
        }
        return null;
    }
}

When consumeWhenReady() sees ready == true, it is guaranteed to see the earlier result = "Finished" write from produce(). In real applications, higher-level concurrency utilities are often clearer for producer-consumer workflows.

Step by Step Execution

Consider this example:

public class StopDemo {
    private volatile boolean running = true;

    public void work() {
        while (running) {
            // Perform a small unit of work.
        }
        System.out.println("Stopped");
    }

    public void requestStop() {
        running = false;
    }
}

Execution trace:

  1. A worker thread calls work(). Initially, running is true.
  2. The worker repeatedly evaluates while (running) and continues working.
  3. Another thread calls requestStop().
  4. That thread writes false to the volatile running field.
  5. A later loop condition check by the worker reads the updated value.
  6. The condition is now false, so the loop exits.
  7. The worker prints Stopped.

Without volatile or another form of synchronization, Java does not provide the same visibility guarantee. The worker could keep observing an old value for an unspecified amount of time.

Real World Use Cases

volatile is most useful when a field represents one independently readable and writable piece of state.

  • Shutdown flags: Stop a background polling, monitoring, or maintenance thread.
  • Cancellation requests: Allow a long-running computation to periodically check whether it should end. Use interruption too when the thread can block in methods such as sleep() or wait().
  • Latest configuration snapshot: Publish an immutable configuration object that readers may replace and read safely.
  • Status fields: Expose a current state such as STARTING, READY, or CLOSED, when no multi-field atomic transition is required.
  • One-time publication patterns: Publish fully initialized data by writing a volatile reference after initialization.

For queues, counters, work scheduling, and complex shared mutable data, prefer established concurrency tools such as BlockingQueue, ConcurrentHashMap, AtomicInteger, executors, and locks.

Take Quiz

Real Codebase Usage

In production code, volatile is often used sparingly and paired with a clear ownership rule.

Guard a loop with a shutdown flag

private volatile boolean closed;

public void runLoop() {
    while (!closed) {
        processNextItem();
    }
}

public void close() {
    closed = true;
}

This is appropriate only if processNextItem() returns regularly. If it blocks, close() may also need to interrupt the worker thread or close the blocking resource.

Publish an immutable snapshot

private volatile AppConfig config = AppConfig.defaults();

public void reload(AppConfig newConfig) {
    config = newConfig;
}

public int timeoutMillis() {
    return config.timeoutMillis();
}

This works best when AppConfig is immutable: once created, its contents do not change. Readers receive either the old complete snapshot or the new complete snapshot.

Choose an atomic class for counters

import java.util.concurrent.atomic.AtomicInteger;

private final AtomicInteger activeRequests = new AtomicInteger();

public void requestStarted() {
    activeRequests.incrementAndGet();
}

An atomic class expresses intent and safely handles read-modify-write operations. It is usually preferable to trying to build counter logic around a volatile integer.

Common Mistakes

Assuming volatile makes count++ safe

This is broken when multiple threads increment the counter:

private volatile int count = 0;

public void increment() {
    count++;
}

count++ is a read-modify-write sequence, not one atomic action. Use AtomicInteger instead:

private final AtomicInteger count = new AtomicInteger();

public void increment() {
    count.incrementAndGet();
}

Using volatile for related fields that must change together

private volatile int x;
private volatile int y;

Each individual read and write is visible, but a reader can still observe a new x with an old y. Put related values in an immutable object and publish one volatile reference, or protect both fields with synchronization.

Writing the volatile flag before the data

ready = true;
result = "Finished";

A consumer that sees ready as true is not guaranteed by this ordering to see the later result write. Prepare ordinary data first, then write the volatile signal:

result = "Finished";
ready = true;

Using a non-volatile stop flag

private boolean running = true;

A second thread changing this field may not be promptly visible to the loop thread. Use volatile, synchronization, or a higher-level cancellation mechanism.

Forgetting that visibility is not coordination

volatile can show a thread the latest state, but it cannot make a check-and-act sequence safe. For example, if (!started) { started = true; } needs synchronization or an atomic compare-and-set operation if only one thread must start the work.

Comparisons

ToolMain guaranteeGood use caseNot sufficient for
volatileVisibility of individual field reads/writes and ordering around that fieldStop flags, immutable snapshot referencesAtomic multi-step updates
synchronizedMutual exclusion, visibility, and orderingProtecting several fields or a critical sectionNon-blocking high-contention counters
AtomicInteger / AtomicReferenceAtomic operations on one valueCounters, compare-and-set state changesCoordinating complex multi-field invariants
LockExplicit mutual exclusion with advanced lock featuresTimed locking, multiple conditions, structured lockingSimple independent state flags
ConcurrentHashMapThread-safe map operationsShared key-value dataMaking a sequence of separate map operations automatically atomic

A useful rule is: choose volatile when a thread only needs to observe or replace one state value. Choose synchronization or atomic operations when threads must make a decision and update state as one action.

Take Quiz

Cheat Sheet

// Declaration
private volatile boolean running = true;
private volatile Config config;
  • volatile applies to fields, not local variables or method parameters.
  • It guarantees visibility of writes to later readers of that field.
  • Reads and writes of the volatile field itself are atomic.
  • It does not make ++, +=, check-then-act logic, or multiple fields atomic.
  • Write data first, then write a volatile ready flag to publish the data.
  • Use it for flags, state references, and immutable snapshots.
  • Use AtomicInteger, AtomicReference, synchronized, or locks for atomic updates and invariants.
  • volatile long and volatile double reads/writes are atomic in modern Java; compound operations are still not atomic.
  • For a blocked thread, changing a volatile flag alone may not wake it; consider interruption or closing the underlying resource.

FAQ

What does volatile mean in Java?

It marks a field as shared state whose writes must be visible to other threads that read the field. It also establishes important ordering guarantees around that read and write.

When should I use volatile in Java?

Use it for simple state such as a stop flag or a reference to a fully initialized immutable configuration object. Use stronger coordination when an update involves multiple steps or fields.

Does volatile make Java code thread-safe?

Not by itself. It solves visibility for one field, but it does not provide mutual exclusion or make compound operations atomic.

Is volatile needed for every shared variable?

No. A variable can also be safely accessed through synchronized, locks, atomic classes, concurrent collections, thread confinement, or immutable objects passed safely between threads.

Why is volatile int count; count++ unsafe?

Incrementing requires separate read, calculation, and write steps. Two threads can read the same old value and overwrite each other's result. Use AtomicInteger.incrementAndGet() or synchronization.

Can volatile replace synchronized?

Only for limited cases involving independent state visibility. It cannot replace synchronized when a critical section must protect several operations or preserve a relationship between fields.

Does a volatile stop flag stop a sleeping thread immediately?

No. A sleeping or blocked thread may not check the flag until it wakes. Use Thread.interrupt() or close the blocking resource when appropriate.

Take Quiz

Mini Project

Description

Build a small background worker that prints work cycles until another thread requests shutdown. The project demonstrates the appropriate use of a volatile boolean: communicating a simple state change from one thread to another.

Goal

Create and stop a worker thread using a volatile shutdown flag.

Requirements

Use a volatile boolean field initialized to true. Create a worker loop that continues while the flag is true. Provide a method that changes the flag to false. Start the worker from main and request shutdown after a short delay. Wait for the worker thread to finish with join().

Take Quiz

Keep learning

Related questions

View more