The Thread Lifecycle
The Thread Lifecycle
Understanding what a thread is doing at any point in time is essential for writing correct concurrent code. Java models a thread's existence as a finite-state machine: a thread moves through a well-defined set of states from the moment it is created until it terminates. Knowing these states — and the methods that trigger transitions between them — lets you reason about program behaviour, diagnose hangs, and write coordination logic that actually works.
The Six Thread States
The Thread.State enum (introduced in Java 5, still current in Java 17+) defines six states:
- NEW — A
Threadobject has been created butstart()has not yet been called. No operating-system thread exists yet. - RUNNABLE — The thread has been started. It is either running on a CPU core right now, or it is ready to run and waiting for the OS scheduler to give it a core. Java does not distinguish between "running" and "ready-to-run" in the state enum.
- BLOCKED — The thread is waiting to acquire an intrinsic monitor lock (a
synchronizedblock or method held by another thread). It cannot proceed until the lock is released. - WAITING — The thread is indefinitely suspended, waiting for another thread to take a specific action. This happens when you call
Object.wait(),Thread.join()with no timeout, orLockSupport.park(). - TIMED_WAITING — Like WAITING but with a maximum duration. Methods that cause this:
Thread.sleep(millis),Object.wait(millis),Thread.join(millis),LockSupport.parkNanos(). - TERMINATED — The thread's
run()method has returned, or an unhandled exception ended it. TheThreadobject still exists in memory but can never be restarted.
You can inspect a thread's current state at runtime with thread.getState(). This is mostly useful for diagnostics and tooling, not for control flow.
sleep — Pausing the Current Thread
Thread.sleep(long millis) causes the currently executing thread to enter TIMED_WAITING for at least the specified number of milliseconds, then return to RUNNABLE. It is a static method — you cannot make another thread sleep.
Two important facts about sleep:
- It does not release monitor locks. If the sleeping thread holds a
synchronizedlock, other threads waiting for that lock remain blocked for the entire duration of the sleep. This is a common source of poor throughput. - The duration is a minimum, not a guarantee. The OS scheduler may resume the thread slightly later than requested, especially on a heavily loaded system.
Run this and you will see the waiter stay BLOCKED for the full 3 seconds.
join — Waiting for Another Thread to Finish
thread.join() causes the calling thread to enter WAITING (or TIMED_WAITING with a timeout argument) until thread reaches TERMINATED. It is the standard way to wait for background work to complete before using its results.
The timed variant worker.join(millis) returns after the timeout even if the worker is still running — always check worker.isAlive() afterward if the result matters.
join() on each. The total wall-clock wait is roughly the duration of the longest task, not the sum — all tasks run in parallel, and the loop simply waits for each one in sequence.
interrupt — Requesting Cancellation
Java's cancellation model is cooperative. You do not forcibly stop a thread; you set its interrupt flag with thread.interrupt() and the thread is expected to notice and stop voluntarily.
There are two ways a thread observes the interrupt flag:
- Blocking methods throw
InterruptedException. When a thread is in WAITING or TIMED_WAITING (sleeping, joining, or waiting on a monitor viaObject.wait()) and another thread callsinterrupt()on it, the blocking call throwsInterruptedExceptionand the interrupt flag is cleared. - Polling with
Thread.interrupted()orthread.isInterrupted(). Long-running CPU-bound loops can check the flag periodically.
catch (InterruptedException e) { /* ignore */ } clears the interrupt flag without acting on it, making the thread unresponsive to cancellation. Either re-throw the exception (if your method signature allows it), or restore the flag with Thread.currentThread().interrupt() before returning.
The correct re-set pattern:
Putting It Together: A State Transition Walkthrough
Consider this sequence for a single thread:
new Thread(task)— state is NEW.thread.start()— JVM creates an OS thread; state becomes RUNNABLE.- Inside
task, callsThread.sleep(500)— state becomes TIMED_WAITING. - Sleep expires — state returns to RUNNABLE.
- Task tries to enter a
synchronizedblock held by another thread — state becomes BLOCKED. - Lock is released — state returns to RUNNABLE.
run()returns — state becomes TERMINATED.
Summary
A Java thread progresses through six well-defined states: NEW → RUNNABLE → (BLOCKED / WAITING / TIMED_WAITING) → TERMINATED. The key methods governing these transitions are Thread.sleep() (voluntary pause without releasing locks), Thread.join() (wait for another thread to finish), and Thread.interrupt() (cooperative cancellation via flag). Mastering these transitions is the foundation for every higher-level concurrency construct covered in the rest of this tutorial.