Understanding Java Memory Model: How I Debugged a Concurrency Bug Without Locks
Digging deep into volatile, instruction reordering, and the JVM Memory Model to fix an elusive race condition.
Understanding Java Memory Model: How I Debugged a Concurrency Bug Without Locks
Digging deep into volatile, instruction reordering, and the JVM Memory Model to fix an elusive race condition.

🧩 Introduction
Debugging concurrency bugs is like chasing ghosts. I recently hit a case where a shared flag updated by one thread wasn’t visible to another — despite all logic saying it should be.
Turns out, I was fighting the Java Memory Model (JMM) — and I won. In this article, I’ll walk you through how I diagnosed and fixed this subtle issue without using locks, and how understanding volatile, memory barriers, and reordering rules can save you hours.
🧠 Background: The Setup
Here’s the simplified version of what I had:
class SharedData {
boolean ready = false;
int value = 0;
}
Thread 1: Writer
data.value = 42;
data.ready = true;
Thread 2: Reader
if (data.ready) {
System.out.println(data.value); // Sometimes prints 0!
}
How is that possible? Didn’t we write value = 42 before setting ready = true?
🔬 The Java Memory Model in Action
The JMM allows the JVM and CPU to reorder instructions as long as it doesn’t break single-threaded semantics.
So internally, this could happen:
// Actual execution order (possible)
data.ready = true;
data.value = 42;
Now, if thread 2 sees ready = true, it may still see the old value of value.
⚠️ What Didn’t Work
I first tried:
synchronized (data) {
data.value = 42;
data.ready = true;
}
But synchronizing only the write side isn’t enough. Thread 2 still reads unsafely.
✅ The Fix: volatile
class SharedData {
volatile boolean ready = false;
int value = 0;
}
Declaring ready as volatile solves the problem.
*volatileensures:*
- Visibility: Writes to
readyare flushed to main memory.- Ordering: All writes before setting
ready = truehappen-before any thread readingready = true.
This effectively acts like a memory barrier.
🧪 Verifying It with a Test
Here’s how I tested it:
public class RaceConditionTest {
static class Shared {
volatile boolean ready = false;
int value = 0;
}
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
Shared shared = new Shared();
Thread writer = new Thread(() -> {
shared.value = 42;
shared.ready = true;
});
Thread reader = new Thread(() -> {
while (!shared.ready);
if (shared.value != 42) {
System.out.println("Race detected: " + shared.value);
}
});
writer.start();
reader.start();
try {
writer.join();
reader.join();
} catch (InterruptedException e) {}
}
}
}
Without volatile, you will see occasional prints like Race detected: 0. With it — nothing.
📋 Summary Table
Here’s a summary of JMM keywords:
+------------+-------------------------------------------------------------+
| Keyword | Meaning |
+------------+-------------------------------------------------------------+
| volatile | Guarantees visibility + ordering for a single variable |
| synchronized | Full memory barrier + mutual exclusion |
| final | Guarantees immutability (post-constructor) |
+------------+-------------------------------------------------------------+
🧾 Conclusion
Understanding the Java Memory Model is crucial if you’re writing low-latency or lock-free code. This bug taught me that:
- Instruction reordering is real
- Visibility is not guaranteed without
volatileorsynchronized volatileis not a performance hack — it's the correct tool for signaling state across threads
Mastering this will make you a more resilient Java engineer — especially when things go wrong in production.
메타데이터
- post_id
- fccf6d89ee05
- slug
- understanding-java-memory-model-how-i-debugged-a-concurrency-bug-without-locks-fccf6d89ee05
- url
- https://medium.com/@connect.hashblock/understanding-java-memory-model-how-i-debugged-a-concurrency-bug-without-locks-fccf6d89ee05
- canonical_url
- https://medium.com/@connect.hashblock/understanding-java-memory-model-how-i-debugged-a-concurrency-bug-without-locks-fccf6d89ee05
- author_url
- https://medium.com/@connect.hashblock
- status
- ok
- fetched_at
- 2026-07-19 00:05:24