Back to Blog
Java

Java Thread States: Lifecycle and Monitoring

java thread states: Understand the six Java thread states, how threads transition between them, and how to observe states in a running program for debugging and monito...

ThreadsConcurrencyThread LifecycleThread DumpJava Debugging
Diagram showing Java thread state transitions from NEW to TERMINATED with monitoring tools.

In Java, every thread moves through a well-defined set of states during its lifetime. These states are part of the Thread.State enum and provide a snapshot of what the thread is currently doing at the JVM level. Understanding java thread states is essential for debugging concurrency issues, interpreting thread dumps, and designing responsive applications.

The Six Thread States and Their Meaning

The Thread.State enum defines six distinct states. Each state represents a specific condition of the thread's execution from the perspective of the JVM.

StateMeaning
NEWThe thread has been created but has not yet started.
RUNNABLEThe thread is executing in the JVM. It may be waiting for CPU resources, but it is not blocked on anything else.
BLOCKEDThe thread is waiting to acquire a monitor lock to enter a synchronized block or method.
WAITINGThe thread is waiting indefinitely for another thread to perform a specific action, such as notify() or join().
TIMED_WAITINGThe thread is waiting for another thread to perform an action, but with a specified waiting time.
TERMINATEDThe thread has completed its execution and has exited.

These states are not arbitrary; they map directly to the thread's interaction with the JVM's scheduling and synchronization mechanisms. A thread in RUNNABLE is either currently executing or ready to execute, but it is not waiting on any lock or condition. BLOCKED and WAITING both indicate that the thread is suspended, but for different reasons.

How Threads Transition Between States

Threads do not jump randomly between states. Transitions are triggered by specific operations. For example, calling start() moves a thread from NEW to RUNNABLE. When a thread attempts to enter a synchronized block and the lock is held by another thread, it becomes BLOCKED. Once the lock is released, it returns to RUNNABLE.

The following example demonstrates a simple thread that moves through several states:

public class ThreadStateDemo { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { try { Thread.sleep(1000); // TIMED_WAITING } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); System.out.println("After creation: " + t.getState()); // NEW t.start(); System.out.println("After start: " + t.getState()); // RUNNABLE Thread.sleep(100); System.out.println("During sleep: " + t.getState()); // TIMED_WAITING t.join(); System.out.println("After join: " + t.getState()); // TERMINATED } }

This code prints the thread's state at different points. Note that the exact state after start() may be RUNNABLE or BLOCKED depending on the operating system scheduler, but it will never be NEW or TERMINATED immediately after start(). The transition to TIMED_WAITING happens when Thread.sleep() is invoked inside the thread's run() method.

Observing Thread States in a Running Program

The Thread.getState() method returns the current state of a thread. This is useful for diagnostics, but it should not be used for coordination in production code. Polling a thread's state repeatedly can introduce overhead and race conditions.

A more practical use is to sample thread states periodically to understand the behavior of a concurrent application. For example, you might log the states of all live threads to detect a deadlock or a thread stuck in BLOCKED:

import java.util.Map; public class StateSampler { public static void printAllThreadStates() { Map<Thread, StackTraceElement[]> allThreads = Thread.getAllStackTraces(); for (Map.Entry<Thread, StackTraceElement[]> entry : allThreads.entrySet()) { Thread t = entry.getKey(); System.out.println(t.getName() + ": " + t.getState()); } } }

This method uses Thread.getAllStackTraces() to obtain a snapshot of all live threads and their stack traces. The state is part of that snapshot. Such a utility can be invoked from a separate monitoring thread or a JMX bean to capture the state of the JVM at a given moment.

What Thread Dumps Reveal About Thread States

A thread dump is a snapshot of all threads in the JVM, including their stack traces and states. Tools like jstack and jcmd generate thread dumps, which are invaluable for diagnosing deadlocks, contention, and infinite waits. In a thread dump, each thread is listed with its state, and the stack trace shows exactly where the thread is blocked.

For example, a thread in BLOCKED will show a stack trace that includes the synchronized block it is trying to enter. A thread in WAITING will show a call to Object.wait(), Thread.join(), or LockSupport.park(). The state alone is not enough to diagnose a problem; you need the stack trace to understand why the thread is in that state.

When analyzing a thread dump, look for groups of threads in BLOCKED or WAITING that are waiting on the same lock. This often indicates lock contention or a deadlock. The jstack tool also prints a deadlock detection summary at the end of the dump, but it is still useful to manually inspect the states.

Common Misconceptions About Thread States

One frequent misunderstanding is that RUNNABLE means the thread is actively using the CPU. In reality, a thread in RUNNABLE may be waiting for the operating system to schedule it. The JVM does not distinguish between "ready to run" and "running" because that distinction is handled by the OS scheduler. Therefore, a thread in RUNNABLE could be waiting for a CPU core, but it is not blocked on any Java-level lock or condition.

Another misconception is that BLOCKED and WAITING are the same. They are not. BLOCKED occurs only when a thread is waiting to enter a synchronized block or method. WAITING occurs when a thread explicitly waits for another thread via wait(), join(), or park(). The distinction matters because the fix for a BLOCKED thread is often to reduce lock contention, while the fix for a WAITING thread might be to ensure that the expected signal is sent.

Using Thread States in Production Monitoring

In production, thread states are a key metric for application health. Monitoring tools like JConsole, VisualVM, and APM agents expose thread state counts. A sudden increase in BLOCKED threads can indicate a lock contention problem, while a large number of TIMED_WAITING threads might suggest excessive use of sleep() or time-based waits.

Polling thread states too frequently in your own code is discouraged because it adds overhead and can alter the behavior you are trying to observe. Instead, rely on JVM-level tools that sample thread states with minimal impact. For example, you can use jcmd Thread.print to get a thread dump on demand, or configure a monitoring agent to collect thread state statistics over time.

When you do need to programmatically inspect thread states, always consider the cost. Creating a thread dump or iterating over all stack traces is relatively expensive and should not be done in a hot path. A better approach is to trigger such diagnostics only when a problem is suspected, such as when a health check fails or a timeout occurs.

Understanding java thread states is not just an academic exercise. It gives you the vocabulary to describe what threads are doing and the tools to investigate concurrency issues. By knowing what each state means and how to observe it, you can quickly identify whether a thread is stuck, waiting for a lock, or simply idle, and then take the appropriate corrective action.

java thread states: Practical Usage and Code Examples | RYUSLOG DEV