Lesson 2 of 728 minModule progress 0%

Module 15: Advanced Concurrency and Async Design

Synchronization, `volatile`, and Shared State

Learn how visibility and atomicity problems appear in shared mutable state. This lesson shows what `synchronized` guarantees, where `volatile` is useful, and how to reason about small safe designs instead of guessing based on timing.

Author

Java Learner Editorial Team

Reviewer

Technical review by Java Learner

Last reviewed

2026-04-17

Java version

Java 25 LTS

How this lesson was prepared: AI-assisted draft, manually expanded into a full lesson guide, and checked against current official Java, Spring, testing, and delivery documentation.

Learning goals

  • Explain the difference between visibility problems and atomicity problems
  • Use `synchronized` to protect a clear critical section
  • Use `volatile` for state flags and simple publication patterns without overstating what it does

Before you start

  • You are comfortable with classes, methods, exceptions, and collections

Lesson roadmap: Start with the mental model, then follow the design choices, common pitfalls, and the practical workflow you should apply in a real project.

A race condition happens when two threads read and write shared data without enough coordination: The result depends on timing, which makes the bug intermittent and painful to reproduce.

synchronized provides mutual exclusion and visibility guarantees: One thread enters the protected block at a time, and changes become visible in a defined way.

volatile is narrower: It guarantees visibility of the latest written value but does not make compound actions such as count++ atomic.

Practical rule: If several steps must stay consistent together, use stronger coordination than volatile.

Visibility vs atomicity: A thread may read an old value even after another thread writes a new one, and a multi-step update may interleave with another thread. Those are different problems and often need different tools.

What synchronized buys you: It does two important jobs at once. It keeps only one thread inside the guarded region at a time and creates the visibility guarantees that make updates reliably observable afterward.

Where volatile fits well: A volatile field is a good fit for stop flags, configuration snapshots, or status values where each read or write is already simple and does not need a larger multi-step invariant.

Design shortcut: The easiest concurrent code is the code that shares less mutable state. Before you add synchronization, ask whether the state can become local, immutable, or confined to one worker.

How to study this module: Run each example more than once, print the current thread name, and change the workload size. Concurrency concepts only become real when you can see scheduling, waiting, and shared-state behavior.

Code review mindset: In concurrent code, correctness comes before throughput. First ask who owns the data, who can mutate it, and what guarantees make each read or write safe.

Production habit: Prefer small, explicit concurrency boundaries. Immutable data, bounded executors, cancellation support, and clear logging are easier to maintain than clever low-level tricks.

Runnable examples

Incrementing shared state is not automatically safe

class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

Expected output

The synchronized method keeps increments from colliding.

A `volatile` flag is useful for cooperative stop signals

class Worker {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public void doWork() {
        while (running) {
            // work loop
        }
    }
}

Expected output

The worker loop can observe the latest value of `running` without extra locking around the flag itself.

Common mistakes

Using `volatile` for `count++` and assuming the update is atomic

`volatile` only handles visibility. Use synchronization or atomic classes for compound updates.

Synchronizing some writes but reading the same field elsewhere without the same discipline

Choose one clear safety strategy per shared field or invariant. Mixed rules are a common source of bugs.

Locking around a huge block of unrelated code

Guard the smallest critical section that preserves correctness so you do not create unnecessary contention.

Mini exercise

Write a short explanation of why `count++` can fail under concurrency, then write one class that fixes it with `synchronized` and one class that uses a `volatile` stop flag for a different kind of problem.

Summary

  • Shared mutable state is the source of many concurrency bugs.
  • `synchronized` coordinates access and visibility.
  • `volatile` handles visibility only, not compound atomic updates.
  • Use `volatile` when visibility is the only missing guarantee.
  • Use `synchronized` when several steps must behave as one protected unit.

Next step

Now compare locks and atomics for more advanced coordination patterns.

Sources used

Advertisement

Lesson check

What does `volatile` guarantee?

Next lesson →