Hello! Welcome to the first lesson of our module on Advanced Java Concurrency & IO.
In our previous module, we delved into functional programming. Your work on parallel streams and understanding the role of the combiner function was an excellent introduction to the complexities that arise when code executes concurrently. That was the "what"; now we dive into the "why" and "how."
Today, we'll build the theoretical bedrock for all of Java's concurrency. Your learning outcome is to explain the Java Memory Model (JMM) and the thread lifecycle. Mastering this is non-negotiable for senior-level interviews. It distinguishes candidates who simply use concurrency utilities from those who deeply understand why they work. This knowledge is the foundation for reasoning about race conditions, visibility issues, and the correctness of concurrent code.
1. The Core Problem: Why Concurrency is Hard
In a single-threaded world, you're protected by a comfortable illusion called as-if-serial semantics. The Java compiler, Just-In-Time (JIT) compiler, and even the CPU are free to reorder your instructions and use complex caching mechanisms to boost performance, as long as the final result within that single thread is the same as if the code executed exactly as you wrote it.
This illusion shatters the moment a second thread enters the picture. That second thread isn't bound by the first thread's "as-if-serial" promise. It might observe the raw, optimized, and reordered reality of memory operations. This leads to two fundamental problems:
- Visibility: When a thread running on one CPU core modifies a variable, that change might be stored temporarily in that core's local cache. Another thread on a different core might continue to see the old, stale value from main memory or its own cache. The change is "invisible" to it.
- Reordering: To optimize execution, compilers and CPUs can reorder instructions. What you write as
x = 42; ready = true;might be executed asready = true; x = 42;. If another thread is waiting forreadyto betruebefore readingx, it might read the uninitialized value ofx.
To begin, let's clarify how memory is organized from a thread's perspective. Each thread has its own private stack for local variables, but all threads share the heap, where objects live. This shared access to the heap is where concurrency issues arise.
The Java Memory Model - The Basics
This video from Jakob Jenkov provides a clear visual explanation of the distinction between thread-local stacks and the shared heap, which is the root of most concurrency challenges.
Watch the first 10 minutes and 49 seconds of the video. Focus on the diagrams that illustrate: How local variables are stored on each thread's stack. How object fields are stored on the shared heap. How two threads accessing the same object can lead to problems.
The hardware architecture underneath the JVM is the reason these visibility and reordering problems exist.

2. The Solution: The Java Memory Model (JMM)
The Java Memory Model is not a physical part of the JVM. It is a formal specification—a contract—that defines the rules governing how threads interact through memory. Its primary goal is to provide guarantees about the visibility and ordering of memory operations, so you can write correct and predictable concurrent code that runs on any hardware.
The JMM's central concept is the Happens-Before relationship.
If action A happens-before action B, it means that the results of action A are guaranteed to be visible to, and ordered before, action B. This is the "visibility bridge" between threads.
The JMM defines several happens-before rules, but for interview purposes, these are the most critical:
- Program Order Rule: Within a single thread, any action happens-before any action that appears later in the code.
- Monitor Lock Rule: An
unlockon a monitor lock (e.g., exiting asynchronizedblock) happens-before a subsequentlockon that same monitor. - Volatile Variable Rule: A
writeto avolatilevariable happens-before every subsequentreadof that samevolatilevariable. - Thread Start Rule: A call to
thread.start()on a new thread happens-before any action in that new thread.
By using constructs like synchronized and volatile, you are inserting these happens-before edges into your program's execution, forcing memory updates to become visible across threads in a predictable order.
Java Memory Model Deep Dive: Visibility, Reordering, and the Truth About Volatile
This article provides one of the clearest explanations of the JMM, focusing on the core problems and the happens-before solution.
Read sections 1 ('The Core Problem') and 2 ('The Solution: "Happens-Before" (HB)'). This will solidify your understanding of why the JMM is necessary and how the happens-before relationship provides the guarantees you need.
3. A Classic Interview Problem: The Stale Flag
A very common interview question is designed to test your understanding of visibility. You are shown a piece of code like this:
public class StaleMate {
private boolean stopRequested = false;
public void run() {
// Thread 1
new Thread(() -> {
System.out.println("Background thread starting...");
while (!stopRequested) {
// do work
}
System.out.println("Background thread stopped.");
}).start();
// Simulate some delay
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread 2 (main thread)
System.out.println("Requesting stop...");
stopRequested = true;
System.out.println("Stop requested.");
}
}
Question: Is the background thread guaranteed to stop?
Answer: No. Without a happens-before relationship, the JIT compiler might heavily optimize the while (!stopRequested) loop. It could decide that since the loop body doesn't modify stopRequested, it can read the value just once, cache it in a CPU register, and loop forever (effectively transforming it into if (!stopRequested) while(true)). The write to stopRequested = true in the main thread might never become visible to the background thread.
The fix is to declare stopRequested as volatile:
private volatile boolean stopRequested = false;
This establishes a happens-before relationship. The write to the volatile variable in the main thread happens-before any subsequent read of it in the background thread, guaranteeing visibility and ensuring the loop terminates.
Java Memory Model in 10 minutes
The following video demonstrates this exact problem and explains how volatile provides the solution by enforcing memory visibility.
Watch from 02:46 to the end. The video covers: A demonstration of a field visibility problem (02:46). An explanation of the happens-before relationship (05:04). The classic boolean flag interview question and its solution (09:32).
Test your understanding!
You've learned that volatile guarantees visibility. What does it not guarantee? Consider the operation volatile int counter = 0; counter++;. Is this operation thread-safe?
Show answer
No, it is not thread-safe. volatile does not guarantee atomicity for compound operations.
The counter++ operation is not a single, atomic step. It's a read-modify-write sequence:
- Read the current value of
counter. - Add one to the value.
- Write the new value back to
counter.
If two threads execute this concurrently, they might both read the same value (e.g., 5), both increment it to 6, and both write 6 back. One increment will be lost. volatile ensures they both see the latest value before starting, but it doesn't prevent them from interleaving the three steps. For this, you would need synchronized or an AtomicInteger.
4. The Thread Lifecycle
The second part of our learning outcome is to understand the thread lifecycle. A thread isn't just "running" or "not running." It moves through a set of well-defined states. Knowing these states and their transitions is crucial for debugging and analyzing concurrent applications.

The following reading provides a concise, interview-ready description of each state.
Java Concurrency Interview Questions (+ Answers)
The Baeldung article on concurrency interview questions has an excellent summary of the thread states. This is exactly the kind of clear, concise answer you should aim to provide in an interview.
Read section 'Q3. Describe the Different States of a Thread and When Do the State Transitions Occur.' Focus on the name of each of the six states and the specific reason a thread enters that state.
Here's a summary of the states for your reference:
NEW: The thread has been created butstart()has not yet been called. It's just an object.RUNNABLE: The thread is eligible to be run by the OS scheduler. This state includes both threads that are actually running on a CPU and those that are ready to run but waiting for their turn. A thread enters this state afterstart()is called.BLOCKED: The thread is waiting to acquire a monitor lock to enter asynchronizedblock or method. It's blocked by another thread holding that lock.WAITING: The thread is in an indefinite wait for another thread to perform a specific action. This happens after calls toObject.wait(),Thread.join(), orLockSupport.park(). It can only leave this state when explicitly notified by another thread.TIMED_WAITING: The thread is waiting for a specified amount of time. It's entered by calling timed versions of methods likeThread.sleep(),Object.wait(timeout), orThread.join(timeout).TERMINATED: The thread has completed its execution (itsrun()method has finished) and can no longer be started.
Conclusion
In this lesson, you've built the fundamental mental model for Java concurrency. Understanding this is what separates professional developers from novices.
Key Takeaways for Your Interview:
- The Problem: Clearly articulate that performance optimizations (instruction reordering and CPU caching) break the naive assumption of line-by-line execution in a multithreaded context, causing visibility and reordering issues.
- The Solution (JMM): Define the JMM as a specification that provides guarantees through the Happens-Before relationship. Be ready to explain what "A happens-before B" means.
- The Tools (
synchronized,volatile): Explain that keywords likesynchronizedandvolatileare your tools to enforce happens-before rules and ensure memory consistency between threads. - The Thread Lifecycle: Be able to list the six thread states (
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED) and explain the key transitions (e.g.,start(), waiting for a lock,wait(),sleep()).
Preview of the Next Lesson:
Now that we have the theoretical model, we'll move on to the practical application of creating and managing threads. In the next lesson, you will implement thread creation using Runnable, Callable, and ExecutorService, explaining the benefits of thread pools. This will be our first step in writing actual concurrent code based on the principles we learned today.