The previous article explained what volatile is and what it is not.
This article focuses on correct use in real code.
The pattern is simple:
- one thread publishes simple state
- another thread must observe that state promptly and correctly
When that is the real problem, volatile is often the cleanest solution.
Problem Statement
Backend code frequently needs to share state like:
- “stop this worker”
- “the latest config snapshot is version 42”
- “this service is now RUNNING”
These are not multi-step transactional updates. They are published state changes.
That is the sweet spot for volatile.
Example 1: Stop Flag
class PollingWorker {
private volatile boolean running = true;
void stop() {
running = false;
}
void runLoop() {
while (running) {
poll();
}
}
void poll() {
}
}
This is the classic correct use case.
Why it works:
- one thread writes the flag
- another thread reads the flag
- there is no compound update around the flag itself
Runnable Example: Shutdown-Aware Poller
import java.util.concurrent.TimeUnit;
public class VolatileVisibilityDemo {
public static void main(String[] args) throws Exception {
PollingWorker worker = new PollingWorker();
Thread thread = new Thread(worker::runLoop, "polling-worker");
thread.start();
TimeUnit.SECONDS.sleep(1);
worker.stop();
thread.join();
System.out.println("Worker exited");
}
static final class PollingWorker {
private volatile boolean running = true;
void stop() {
running = false;
}
void runLoop() {
while (running) {
// Simulate polling work.
}
}
}
}
This is intentionally minimal because the point is the visibility guarantee, not the work inside the loop.
Example 2: Immutable Config Snapshot
class ConfigManager {
private volatile AppConfig current =
new AppConfig("https://payments.internal", 3);
AppConfig current() {
return current;
}
void reload(AppConfig newConfig) {
current = newConfig;
}
}
final class AppConfig {
private final String endpoint;
private final int retries;
AppConfig(String endpoint, int retries) {
this.endpoint = endpoint;
this.retries = retries;
}
}
This pattern is strong because:
- the reference is published with
volatile - the object itself is immutable
- readers observe whole snapshots rather than in-place mutation
This is a very common production pattern.
Example 3: Service Lifecycle State
enum ServiceState {
STARTING, RUNNING, STOPPING, STOPPED
}
class ServiceLifecycle {
private volatile ServiceState state = ServiceState.STARTING;
void markRunning() {
state = ServiceState.RUNNING;
}
ServiceState state() {
return state;
}
}
This works because the field represents one published status value.
If later logic depends on several fields moving together with the state, then volatile alone is no longer enough.
Production-Style Scenario
Imagine a routing layer in a payment service:
- a background refresh task builds a new immutable routing table snapshot
- request threads read the current snapshot on every request
- the refresh task swaps the reference when a new version is ready
That is an excellent volatile use case because:
- one field represents the current published snapshot
- readers do not modify it
- the new version replaces the old version atomically at the reference level
This is far better than mutating a shared routing object in place.
When volatile Is a Good Fit
Use it when:
- the field stands alone as the state you care about
- the update is simple publication, not a compound transaction
- the referenced object is immutable or effectively immutable
- the problem is visibility, not exclusion
When It Is the Wrong Fit
Do not use volatile alone when:
- several related fields must change together
- you need check-then-act correctness
- updates depend on the previous value
- the object behind the reference is still being mutated concurrently
Safe visibility of a reference is not the same thing as safe mutation of the object graph behind it.
Common Mistakes
- putting
volatileon a reference to a mutable map and then updating the map from multiple threads - using
volatile intfor counters - mixing a volatile flag with unsafely shared associated data
The field may be visible while the surrounding design is still broken.
Key Takeaways
volatileis practical and powerful when the real need is visibility of one published value.- Stop flags, immutable config snapshots, and lifecycle status fields are strong use cases.
- It works best when the field represents one clear piece of truth.
- Once the logic becomes compound or multi-field, move to atomics or locking.
Next post: Why volatile Does Not Make Compound Actions Atomic
Comments