Fundamentals 14 min read

When Java volatile Falls Short: Visibility, Happens‑Before, and Performance

This article explains how Java’s volatile keyword ensures variable visibility across threads, details its full visibility and happens‑before guarantees, explores instruction reordering challenges, outlines scenarios where volatile alone is insufficient, and discusses performance considerations and alternative synchronization mechanisms.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
When Java volatile Falls Short: Visibility, Happens‑Before, and Performance

Java volatile keyword marks a variable as stored in main memory, meaning every read of a volatile variable fetches from main memory and every write updates main memory, not just the CPU register.

Since Java 5, the guarantees of volatile go beyond simple main‑memory reads and writes.

Variable Visibility Problems

The volatile keyword ensures that changes to a variable become visible to all threads. In a multithreaded program, if a thread works with a non‑volatile variable, the JVM may cache the value in a CPU register, leading to visibility issues.

For non‑volatile variables, the JVM provides no guarantees about when data is read from or written to main memory, which can cause several problems.

public class SharedObject {
    public int counter = 0;
}

Assume two threads access a shared counter. Thread 1 increments the counter, while both threads may read its value.

public class SharedObject {
    public volatile int counter = 0;
}

If counter is not declared volatile, the JVM cannot guarantee when the value is written back to main memory, so the CPU register value may diverge from main memory.

This situation is called a “visibility” problem: an update by one thread is not visible to others.

Java volatile Visibility Guarantee

Declaring a variable volatile ensures that all writes to it are immediately written to main memory and all reads fetch directly from main memory.

public class SharedObject {
    public volatile int counter = 0;
}

Marking a variable as volatile guarantees its visibility to other threads.

Full volatile Visibility Guarantee

If thread A writes to a volatile variable, then any thread B that reads that same volatile variable will also see all other variables that were visible to A before the write.

If thread A reads a volatile variable, all variables visible to A at that moment are re‑read from main memory.

Example:

public class MyClass {
    private int years;
    private int months;
    private volatile int days;

    public void update(int years, int months, int days) {
        this.years = years;
        this.months = months;
        this.days = days;
    }
}

When update() writes to days, the values of years and months are also flushed to main memory because of the happens‑before relationship.

public class MyClass {
    private int years;
    private int months;
    private volatile int days;

    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

    public void update(int years, int months, int days) {
        this.years = years;
        this.months = months;
        this.days = days;
    }
}

Instruction Reordering Challenges

To improve performance, the JVM and CPU may reorder instructions as long as program semantics remain unchanged.

int a = 1;
int b = 2;
a++;
b++;

The above can be reordered without changing meaning, but if a volatile variable is involved, reordering becomes problematic.

public class MyClass {
    private int years;
    private int months;
    private volatile int days;

    public void update(int years, int months, int days) {
        this.days = days;
        this.months = months;
        this.years = years;
    }
}

Here the writes to months and years may occur before the write to days, breaking the visibility guarantee.

Java volatile Happens‑Before Guarantee

Operations on other variables cannot be reordered to occur after a write to a volatile variable.

Operations on other variables cannot be reordered to occur before a read of a volatile variable.

These rules ensure the visibility guarantees of volatile are respected.

volatile Is Not Always Enough

Even with volatile, if multiple threads read‑modify‑write the same variable, race conditions can occur because the read‑modify‑write sequence is not atomic.

public class Counter {
    public volatile int counter = 0;
}

When two threads increment the counter simultaneously, each may read the same stale value, increment it, and write back, resulting in lost updates.

In such cases you need stronger synchronization, such as synchronized blocks or atomic classes from java.util.concurrent (e.g., AtomicLong, AtomicReference).

When Is volatile Enough?

If only one thread writes to a volatile variable and others only read it, the readers will see the latest value. If multiple threads both read and write, volatile alone is insufficient.

The volatile keyword works for both 32‑bit and 64‑bit variables.

Performance Considerations of volatile

Reading or writing a volatile variable forces access to main memory, which is slower than accessing a CPU register or cache. It also prevents instruction reordering, a common performance optimization. Therefore, use volatile only when you truly need to enforce visibility.

Understanding how volatile works helps you decide when it is appropriate and when stronger synchronization is required.

Javavolatilehappens-beforememory visibility
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.