Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Thread Lifecycle and States

Recommended resources

In our previous lesson, we established the fundamental distinction between processes and threads, highlighting that a thread is the basic unit of execution managed by the operating system's scheduler. Now, we'll delve into precisely how the OS manages these units throughout their existence. This brings us to the concept of the thread lifecycle.

Just as a process moves through states like 'ready', 'running', and 'terminated', so too does a thread. Understanding this lifecycle—the sequence of states a thread passes through from its creation to its termination, and the events that trigger transitions between them—is crucial for debugging and designing correct, efficient concurrent programs.

The Lifecycle of a Thread: A High-Level View

At its core, a thread's life can be visualized as a path through a few fundamental states. A thread is created, becomes ready to run, gets selected by the scheduler to run on a CPU, might pause its execution to wait for something, and eventually finishes its work.

The following diagram shows a generalized model of these states.

This flowchart illustrates a common, high-level model of a thread's lifecycle. A thread is created, enters a ready state, is dispatched to a running state, and can cycle between running and various waiting states before it is finally finished.

Let's break down these OS-level states:

  • Creation (or New): The thread has been conceptualized in your program (e.g., an object has been instantiated), but the operating system has not yet allocated the resources for it to run. It's an inert entity.
  • Ready (or Runnable): The thread has been created and is fully prepared to execute. It's now in a pool of ready threads, waiting for the OS scheduler to assign it to a CPU core.
  • Running: The scheduler has selected this thread, and its instructions are actively being executed by a CPU.
  • Blocked (or Waiting): The thread cannot proceed, even if a CPU is available. It is paused, pending some external event, such as the completion of an I/O operation, the release of a lock it needs, or a signal from another thread.
  • Terminated (or Finished): The thread has completed its execution, either by finishing its task normally or due to an unrecoverable error. It can no longer be run.

A More Granular Look: Distinguishing Types of Waiting

While the high-level model is a good start, most modern programming environments, like the Java Virtual Machine (JVM) or .NET runtime, expose a more detailed set of states. This granularity is particularly useful because it differentiates why a thread is not running.

The diagram below provides a more detailed state model and connects states to the methods that cause them, which is common in many object-oriented languages.

This diagram shows a detailed thread lifecycle model, including the transitions caused by common methods like `start()`, `sleep()`, `wait()`, and `join()`. It distinguishes between `Blocked`, `Waiting`, and `Timed_Waiting` states.

Let's explore these states in more detail. The following reading from AlgoMaster.io provides an excellent breakdown.

Thread Lifecycle And States | Concurrency Interview - AlgoMaster.io

This article offers a clear and detailed explanation of the common states in a thread's lifecycle, contrasting the high-level OS view with the more granular states exposed by languages like Java. It explains the purpose of each state and the actions that trigger transitions.

Please read the following sections: Start from the top, reading the introduction and the description of universal OS-level states. Then, proceed to the main section, "The Thread States". Read the detailed descriptions for each of the seven states, from NEW to TERMINATED. Pay close attention to the distinction between BLOCKED, WAITING, and TIMED_WAITING. Also, note the important point made about why it's difficult for user-space programs to distinguish between RUNNABLE and RUNNING.

As you've just read, the key refinement in this detailed model is the separation of non-runnable states:

  • BLOCKED: The thread is waiting to acquire a monitor lock (e.g., trying to enter a synchronized block in Java or lock a mutex) that is currently held by another thread. This is a specific form of waiting related to contention for mutual exclusion.
  • WAITING: The thread is in an indefinite wait for another thread to perform a specific action. It has been explicitly "parked" by calling a method like Object.wait() or Thread.join(). It will only move back to the RUNNABLE state when explicitly woken up by another thread (e.g., via Object.notify()) or when the thread it joined has terminated.
  • TIMED_WAITING: Similar to WAITING, but with a timeout. The thread will wake up if notified by another thread OR if the specified time elapses. This is the state entered by calls like Thread.sleep(long millis) or Object.wait(long timeout).

How Synchronization Primitives Drive State Transitions

These state transitions are not abstract concepts; they are the direct consequence of invoking specific functions from threading libraries and synchronization primitives provided by the operating system. Since you're familiar with OS concepts like semaphores and deadlocks, we can connect these states directly to the underlying mechanisms, such as those found in the POSIX threads (pthreads) standard.

When a thread calls pthread_mutex_lock() and the mutex is already held, the kernel doesn't wastefully spin the CPU. Instead, it changes the thread's state to Blocked and moves it to a wait queue associated with that mutex. The scheduler then picks another thread from the Ready queue to run. When the mutex is later released, the kernel moves one of the waiting threads from the wait queue back to the Ready queue, making it eligible for execution again.

The following text from a guide on POSIX threads details how specific synchronization calls affect a thread's state.

[PDF] POSIX Threads (pthreads) - Rohini College

This document describes the thread states within the POSIX model and explicitly links them to the synchronization mechanisms that cause state changes.

Please read the following parts: First, read the section on "Thread States" to see how they are defined in the POSIX context, from the definitions. Next, read the section on "Thread joining" to understand a concrete example of a state transition, focusing on the explanation of pthread_join(). Finally, read the introduction to "Synchronization Mechanisms and Their Impact on Thread State" and the detailed descriptions for Mutexes and Condition Variables, from this overview. This will solidify your understanding of how locking and signaling directly manipulate a thread's position in its lifecycle.


Summary of State Transitions

To consolidate everything, the lifecycle is a graph where the nodes are states and the edges are the events or function calls that trigger the transitions. The table below, also from the AlgoMaster article, provides a concise summary of these transitions.

Thread Lifecycle And States | Concurrency Interview - AlgoMaster.io

This table provides a quick reference for the most common transitions between thread states.

Study the summary table to review the entire lifecycle at a glance.


Conclusion

In this lesson, we have mapped out the entire lifecycle of a thread. We started with a simple OS-level model (Ready, Running, Blocked, Terminated) and progressed to a more detailed model that distinguishes between different reasons for waiting (Blocked, Waiting, Timed_Waiting). Most importantly, we've connected these abstract states to concrete programming constructs and OS synchronization primitives like join(), sleep(), and mutex locks. A thread is not just an execution path; it is a stateful entity managed by the OS, and its current state dictates whether it is consuming CPU cycles, waiting for the scheduler, or paused for an external event.

In our next lesson, we will explore a crucial implementation detail that significantly impacts how these states are managed: the difference between user-level threads and kernel-level threads. We will see that whether the operating system is even aware of your application's threads has profound implications for scheduling, performance, and parallelism.