Java Concurrency + Memory Visibility

What is the difference between volatile and synchronized?

volatile gives visibility guarantees for a variable. synchronized gives visibility plus mutual exclusion, meaning only one thread can enter the critical section at a time.

ConcurrencyJava Memory ModelSenior Java

The Short Answer

volatile makes changes to a variable visible across threads. In other words, if you have 100 threads, and thread 57 updates a volatile variable, then the other threads will see the latest value instead of working with an old cached copy.

But volatile does not stop multiple threads from updating the value at the same time. So if several threads are modifying the variable together, you can still get race conditions and lost updates.

synchronized also gives visibility guarantees, but it adds mutual exclusion too: only one thread can enter the synchronized block at a time.

Meaning that while one thread is updating the protected value, another thread cannot enter and modify it simultaneously. That is why synchronized can protect critical sections and keep multi-step operations consistent.

volatile is about visibility. synchronized is about visibility plus locking.

The Real Problem

In single-threaded code, when you update a variable, the next line of code sees the update. Simple.

In multi-threaded code, one thread changing a value does not automatically mean every other thread immediately sees the newest value.

Without volatile

Main memory: running = true
Thread A sets running = false
Thread B may still see true

Thread B may keep working with an old cached value because there is no visibility guarantee.

With volatile

volatile boolean running
Thread A writes false
Thread B sees false

volatile tells Java that reads and writes to this variable must be visible across threads.

Good Use Case for volatile

volatile is useful for a simple flag where one thread signals another thread to stop.

java
class Worker {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public void run() {
        while (running) {
            // do work
        }
    }
}

Here, volatile is enough because reading and writing a boolean flag is simple. We only need visibility.

Where volatile Is Not Enough

volatile does not make compound operations atomic.

java
private volatile int count = 0;

public void increment() {
    count++;
}

This looks like one operation, but it is actually a read, modify, and write.

read count
add 1
write count
  • Two threads can still read the same old value and overwrite each other's update.
  • Imagine count is currently 10.
  • Thread 1 reads 10 and decides to increment it to 11.
  • But before Thread 1 writes 11 back to memory, 200 other threads may also read the old value 10 and independently decide that the next value should be 11.
  • The result is that many threads may all write back 11, even though the correct final value should have been much larger.
volatile guarantees visibility of the latest written value, but it does not make multi-step operations like count++ atomic.

When synchronized Is Better

Use synchronized when a block of code must be protected so only one thread can execute it at a time.

java
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

Now increment is protected. One thread enters, updates the value, and exits. Another thread cannot enter that method at the same time.

Mental Model

volatile

Visibility Signal

“Everyone should see the latest value.”

synchronized

Locked Room

“Only one thread may enter this critical section.”

Interview-Friendly Answer

volatile guarantees visibility of changes to a variable across threads, but it does not protect compound operations like count++. synchronized provides visibility and mutual exclusion, so it is used when a critical section must be executed by only one thread at a time.

Common Interview Follow-Ups

Can volatile replace synchronized?

Only in simple visibility cases. It cannot replace synchronized when you need atomic multi-step updates.

Is count++ safe if count is volatile?

No. count++ is still read-modify-write, so multiple threads can lose updates.

Does synchronized also provide visibility?

Yes. Entering and exiting a synchronized block creates memory visibility guarantees.

When would you use AtomicInteger instead?

Use AtomicInteger when you need atomic operations like incrementAndGet without writing synchronized blocks yourself.

Final Takeaway

Use volatile for simple shared state visibility. Use synchronized when you need to protect a critical section. If the operation is read-modify-write, volatile alone is usually not enough.