Concurrency & Multi-threading
Thread Lifecycle & States
Master the lifecycle states of a thread, including state transition diagrams, scheduling, and control methods like wait, notify, sleep, and join.
A thread is a dynamic entity. From the moment it is instantiated to the moment it terminates, it transitions through several execution states defined by the JVM and the host operating system. To debug thread hangs, deadlocks, and CPU spikes, you must master the state machine governing the thread lifecycle.
1. Learning Objectives
- Identify the six primary thread states in the JVM/OS lifecycle.
- Understand the exact conditions that cause a thread to transition between
RUNNABLE,BLOCKED,WAITING, andTIMED_WAITING. - Differentiate between the mechanics of
Thread.sleep(),Object.wait(),Thread.yield(), andThread.join(). - Use thread dumps to diagnose application performance bottlenecks and deadlocks.
- Explain the stack frame transitions that occur when threads block on synchronization monitors.
2. Problem & Naive Solution
Suppose you are building an order processing system. When an order is placed, a background worker thread needs to wait for a payment confirmation event from an external gateway, process the shipping labels, and exit.
The Naive Solution (Busy Waiting)
A developer implements the waiting mechanism by using an active "busy loop" that constantly checks a flag in shared memory:
3. Issues with the Naive Approach
- Extreme CPU Exhaustion: The busy-wait thread remains in the
RUNNABLEstate, consuming 100% of a CPU core's cycles just executing empty jump checks. This leaves fewer resources for other threads, degrading system performance. - Poor Thread Scheduling Cooperation: The thread does not yield its time slice to other active threads, preventing the OS scheduler from optimizing thread execution.
4. State Transition Diagram
A thread transitions through these key states during its lifecycle:
5. Theory: Thread State Explanations
Let's define the six primary thread states in detail:
A. The Six JVM Thread States
- NEW: The thread has been created (
new Thread()) but not yet started. It is simply an object on the heap and has no OS execution context allocated. - RUNNABLE: The thread is executing or ready to execute in the JVM. It may be currently running on a CPU core, or waiting in the OS ready queue for its next time slice.
- BLOCKED: The thread is waiting to acquire a monitor lock (e.g., waiting to enter a
synchronizedblock or method). The OS suspends it, releasing CPU resources. - WAITING: The thread is waiting indefinitely for another thread to perform a specific action (e.g., calling
object.wait(),thread.join(), orLockSupport.park()). It remains suspended until explicitly notified. - TIMED_WAITING: Similar to
WAITING, but with a timeout limit (e.g.,Thread.sleep(ms),object.wait(ms),thread.join(ms)). The thread automatically wakes up and returns to theRUNNABLEstate once the timeout expires. - TERMINATED: The thread has finished executing its task or exited due to an unhandled exception. It cannot be restarted.
B. Controlling Thread States
You can control thread transitions using the following core methods:
Thread.sleep(ms): Suspends the thread for a specified duration, transitioning it toTIMED_WAITING. The thread retains any monitor locks it holds.Object.wait(): Used inside a synchronized context. The thread yields its CPU time slice, releases its monitor lock, and enters theWAITINGstate. This prevents busy waiting.Object.notify() / notifyAll(): Wakes up one (or all) threads waiting on the object's monitor, moving them back to theRUNNABLEstate.Thread.join(): Blocks the calling thread until the target thread terminates, entering theWAITINGstate.Thread.yield(): Suggests to the OS scheduler that the current thread is willing to yield its remaining CPU time slice to other runnable threads of equal priority. The scheduler is free to ignore this suggestion.
6. Syntax Explanation
To monitor and control thread states, languages provide specific APIs:
-
Java: Querying states uses
thread.getState(), which returns aThread.Stateenum value (e.g.RUNNABLE,BLOCKED, etc.). -
Python: The
threadingmodule does not have a formal state enum, but it provides helper boolean methods liket.is_alive()and lock wait checks. -
C++: Creating threads uses
std::thread. You can sleep usingstd::this_thread::sleep_for(), and query thread IDs to track execution paths.
7. Step-by-Step Implementation
- Instantiate the Worker: Create a task containing states that can be locked or slept.
- Query Initial State: Read
t.getState()before calling.start()to verify theNEWstate. - Observe Running/Sleep: Start the thread and read its state while it executes or sleeps (
TIMED_WAITING). - Trigger Lock Contention: Spawn a second thread that attempts to acquire the same lock, and monitor its
BLOCKEDstate. - Join and Verify Termination: Wait for completion and confirm the thread enters the
TERMINATEDstate.
8. Complete Code (Thread State Transition Monitor)
This program creates a worker thread and monitors its state transitions as it starts, sleeps, acquires locks, and terminates.
9. Code Walkthrough
- Lock Blocking Simulation: In the Java program, the main thread acquires the lock object monitor *before* starting the worker thread. When the worker wakes up from its
Thread.sleep(), it attempts to entersynchronized (lock). Since the main thread still holds the lock monitor, the worker thread transitions fromRUNNABLEtoBLOCKED. - TIMED_WAITING Verification: The main thread sleeps for 200ms using
Thread.sleep(200). During this window, the worker thread executes its ownThread.sleep(1000). Queryingworker.getState()during this period returnsTIMED_WAITING.
10. Internal Working (JVM Monitors & Lock Queues)
When threads transition between states, the JVM manages them using internal synchronization queues:
- Monitor Entry Set (BLOCKED Queue): If multiple threads attempt to enter a synchronized block on an object whose monitor is already held, the JVM transitions those threads to the
BLOCKEDstate and places them in the monitor's Entry Set queue. When the active thread exits the block, the OS scheduler wakes up one of these blocked threads to acquire the monitor. - Wait Set (WAITING Queue): When a thread calls
object.wait(), it yields its CPU time slice, releases the monitor lock, and enters the monitor's Wait Set queue, transitioning to theWAITINGstate. Callingobject.notify()moves a thread from the Wait Set back to the Entry Set, where it must compete to re-acquire the lock.
11. Complexity Analysis
- Time Complexity: State queries (
getState()) are $O(1)$ operations, as they read cached thread status flags. - Space Complexity: Thread stack allocation is $O(1)$ (usually 1MB per thread).
12. Best Practices
- Never Use Deprecated Methods: Never call
Thread.stop(),Thread.suspend(), orThread.resume(). They are deprecated because they release all monitor locks instantly, leaving shared objects in corrupted or inconsistent states, which can cause deadlocks. Use flags or interruption instead. - Always Wait in a Loop: When calling
wait(), always wrap it in awhileloop that checks the condition, rather than anifstatement. This protects against spurious wakeups (where a thread wakes up without being notified).
13. Common Mistakes
- Calling
wait()Without Holding the Lock: Invokingwait()on an object without wrapping it in asynchronizedblock on that same object. This throws anIllegalMonitorStateExceptionat runtime. - Conflating WAITING with BLOCKED: Assuming a thread waiting for an HTTP response is
BLOCKED. It is actually in theRUNNABLEorTIMED_WAITINGstate from the JVM's perspective, as it is blocked on OS system calls rather than a JVM monitor lock.
14. Interview Discussion
Answer: A spurious wakeup occurs when a thread wakes up from a
wait() state without receiving a notify() or interrupt() signal. It is a side effect of OS-level thread scheduler optimizations. To prevent it, always check the condition variable in a while loop:
Thread.sleep() release monitor locks?Answer: No. If a thread holds a lock and calls
Thread.sleep(ms), it transitions to TIMED_WAITING but retains the lock. Any other thread attempting to acquire that lock will block until the sleeping thread wakes up and exits the synchronized block.
Answer: - BLOCKED: The thread is waiting to acquire a JVM monitor lock (e.g., to enter a
synchronized block).
- WAITING: The thread is waiting for another thread to perform a specific action (e.g., calling notify() or completing via join()).
15. Practice Exercises
- Easy: Write a program that instantiates a thread, prints its state, starts it, and prints its state again.
- Medium: Build a utility that takes a thread reference and polls its state every 50ms, logging any state transitions.
- Hard: Write an application that intentionally simulates all six JVM thread states (
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED) and capture them using a thread dump.
16. Challenge Problem
Design a Thread State Diagnostic Monitor. The monitor runs as a background daemon thread in an application. It tracks all active threads. If any thread remains in the BLOCKED or WAITING state for more than 10 seconds, the monitor must capture a thread dump, identify which thread holds the blocking lock, and log a warning to prevent deadlock freezes.
17. Summary & Cheat Sheet
| Thread State | Trigger / Cause | Release Trigger |
|---|---|---|
| NEW | Instantiated object (new Thread()) |
Invoking start() |
| RUNNABLE | Calling start() or waking up from sleep/wait |
OS time slice expiry, locking, or sleeping |
| BLOCKED | Waiting to acquire a monitor lock | Active lock holder releases monitor |
| WAITING | wait(), join(), LockSupport.park() |
notify(), notifyAll(), or thread termination |
| TIMED_WAITING | sleep(t), wait(t), join(t) |
Timeout duration expires or notification received |
| TERMINATED | Task code completes or uncaught exception thrown | None (state is terminal) |
18. Quiz
1. Which thread state represents a thread that is instantiated but not yet executed?
A) RUNNABLE
B) NEW (Correct)
C) WAITING
2. What JVM state is a thread in when it is waiting to enter a synchronized block?
A) WAITING
B) BLOCKED (Correct)
C) TIMED_WAITING
3. Does calling Thread.sleep() release the monitor locks held by the sleeping thread?
A) Yes, locks are released immediately
B) No, the thread retains locks during sleep (Correct)
C) Only when using virtual threads
4. To prevent spurious wakeups, how should you invoke the wait() method?
A) Inside an if block
B) Inside a while loop checking the condition (Correct)
C) Without any synchronization block
5. Which method yields the remaining time slice of the current thread to the OS scheduler?
A) Thread.sleep()
B) Thread.yield() (Correct)
C) Thread.stop()
6. What exception is thrown if you call wait() without acquiring the object's monitor lock?
A) NullPointerException
B) IllegalMonitorStateException (Correct)
C) InterruptedException
7. What state does a thread transition to when it finishes executing its run method?
A) WAITING
B) TERMINATED (Correct)
C) NEW
8. Where does a thread go when it releases its monitor lock and calls wait()?
A) The monitor's Entry Set
B) The monitor's Wait Set (Correct)
C) The CPU registers
9. What state does calling thread.join() transition the caller thread to?
A) RUNNABLE
B) WAITING (Correct)
C) BLOCKED
10. Why are methods like Thread.stop() deprecated?
A) They occupy too much memory
B) They release all monitor locks instantly, potentially leaving shared data structures in inconsistent states (Correct)
C) They slow down the OS clock speed
19. Next Lesson Preview
In the next lesson, we will explore Race Conditions & Critical Sections. We will examine how unsynchronized shared memory access corrupts data, study how the CPU reorders instructions, and learn about the volatile memory visibility protocol!
Related Topics
- Introduction to ConcurrencyUnderstand the fundamentals of concurrency, why it is essential for modern high-performance LLD, and how the operating system schedules interleaved execution.
- Concurrency vs ParallelismDifferentiate between logical task structures and physical hardware execution, understanding cache coherence, false sharing, and hardware limits.
- Processes vs ThreadsMaster the operating system level differences between processes and threads, comparing virtual memory layouts, context switching costs, and communication boundaries.