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
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