ReviseAlgo Logo

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.

Last Updated: June 26, 2026 25 min read

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, and TIMED_WAITING.
  • Differentiate between the mechanics of Thread.sleep(), Object.wait(), Thread.yield(), and Thread.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 RUNNABLE state, 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 synchronized block 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(), or LockSupport.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 the RUNNABLE state 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 to TIMED_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 the WAITING state. This prevents busy waiting.
  • Object.notify() / notifyAll(): Wakes up one (or all) threads waiting on the object's monitor, moving them back to the RUNNABLE state.
  • Thread.join(): Blocks the calling thread until the target thread terminates, entering the WAITING state.
  • 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 a Thread.State enum value (e.g. RUNNABLE, BLOCKED, etc.).
  • Python: The threading module does not have a formal state enum, but it provides helper boolean methods like t.is_alive() and lock wait checks.
  • C++: Creating threads uses std::thread. You can sleep using std::this_thread::sleep_for(), and query thread IDs to track execution paths.

7. Step-by-Step Implementation

  1. Instantiate the Worker: Create a task containing states that can be locked or slept.
  2. Query Initial State: Read t.getState() before calling .start() to verify the NEW state.
  3. Observe Running/Sleep: Start the thread and read its state while it executes or sleeps (TIMED_WAITING).
  4. Trigger Lock Contention: Spawn a second thread that attempts to acquire the same lock, and monitor its BLOCKED state.
  5. Join and Verify Termination: Wait for completion and confirm the thread enters the TERMINATED state.

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 enter synchronized (lock). Since the main thread still holds the lock monitor, the worker thread transitions from RUNNABLE to BLOCKED.
  • TIMED_WAITING Verification: The main thread sleeps for 200ms using Thread.sleep(200). During this window, the worker thread executes its own Thread.sleep(1000). Querying worker.getState() during this period returns TIMED_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 BLOCKED state 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 the WAITING state. Calling object.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(), or Thread.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 a while loop that checks the condition, rather than an if statement. This protects against spurious wakeups (where a thread wakes up without being notified).

13. Common Mistakes

  • Calling wait() Without Holding the Lock: Invoking wait() on an object without wrapping it in a synchronized block on that same object. This throws an IllegalMonitorStateException at runtime.
  • Conflating WAITING with BLOCKED: Assuming a thread waiting for an HTTP response is BLOCKED. It is actually in the RUNNABLE or TIMED_WAITING state from the JVM's perspective, as it is blocked on OS system calls rather than a JVM monitor lock.

14. Interview Discussion

Q: What is a spurious wakeup and how do you prevent it?
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:
Q: Does 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.
Q: What is the difference between the BLOCKED and WAITING states?
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!