W3docs

Java Thread Lifecycle

The states a Java thread can be in — NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED — and how they change.

Java Thread Lifecycle

A Java thread doesn't have many states — six, all of them values of the Thread.State enum. But those six are the vocabulary of thread dumps, profilers, and every "why is my program stuck" investigation you'll ever do. Knowing what each state means and what transitions are possible is what turns a thread dump from a wall of stack traces into a diagnosis.

The six states

public enum Thread.State {
  NEW,                  // created, never started
  RUNNABLE,             // started; running or ready to run on a CPU
  BLOCKED,              // waiting for a monitor lock to enter a synchronized block
  WAITING,              // parked indefinitely (Object.wait, Thread.join, LockSupport.park)
  TIMED_WAITING,        // parked with a timeout (sleep, wait(ms), join(ms), park(nanos))
  TERMINATED            // run() has returned
}

The transitions allowed between them form a simple lifecycle:

   NEW
    |  start()
    v
RUNNABLE  <----------+--------+-------+
    |   |            |        |       |
    |   | enters     | wakes  | timeout
    |   v sync       | from   | expires
    |  BLOCKED       | wait/  |
    |   |            | join   |
    |   | acquires   |        |
    |   v lock       |        |
    +-> RUNNABLE     |        |
    |                |        |
    | wait/join/park |        |
    v                |        |
WAITING -------------+        |
    |                         |
    | wait(ms)/join(ms)/sleep |
    v                         |
TIMED_WAITING ----------------+
    |
    | run() returns
    v
TERMINATED

Each state corresponds to something visible in a thread dump. Let's walk them.

NEW

A Thread you've constructed but never called start() on. No OS resources have been allocated; nothing is running. The only transitions out are:

  • start()RUNNABLE
  • The thread is garbage-collected without ever running

You can call start() exactly once. A second call throws IllegalThreadStateException.

RUNNABLE

"The thread is alive and is either running on a CPU right now or is ready to run." Java collapses the OS's "running" and "runnable" into a single state — there's no way to tell from Thread.State alone whether the thread is currently consuming CPU. The OS scheduler decides which RUNNABLE threads actually get a core at any moment.

A RUNNABLE thread is also the state a thread is in when it's blocked on I/O (InputStream.read, Socket.read, FileChannel.read). This surprises people: the thread is "ready to run" only in the sense that nothing in the JVM is gating it. The OS knows the thread is waiting for the disk; the JVM doesn't, so it reports RUNNABLE. If you see a thread dump where a thread is RUNNABLE and its top frame is socketRead0 or similar, the thread is blocked on a syscall — not burning CPU.

BLOCKED

The thread is sitting at the door of a synchronized block waiting for the monitor lock. Some other thread has it; this one queued up. As soon as the holder releases, one of the waiters wins the lock and transitions back to RUNNABLE.

BLOCKED is specific to synchronized — the intrinsic-lock mechanism the JVM bakes in. Code waiting on a ReentrantLock does not show BLOCKED; it shows WAITING (because ReentrantLock is implemented on top of LockSupport.park). That's a small but important distinction when you're reading dumps.

The classic thread-dump signature for BLOCKED:

"worker-3" #19 prio=5 ... waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
   at com.acme.Cache.put(Cache.java:42)
   - waiting to lock <0x000000076ab8e220> (a java.util.HashMap)
   at com.acme.Cache.miss(Cache.java:67)

Two pieces of information: which monitor you're waiting on (<0x000000076ab8e220>) and what method is sitting at the door. Search the same dump for - locked <0x000000076ab8e220> and you've found the thread that holds it.

WAITING

The thread chose to wait indefinitely. Three things put a thread here:

  • Object.wait() — releases the monitor and parks until somebody calls notify/notifyAll.
  • Thread.join() — without a timeout, parks until the target thread terminates.
  • LockSupport.park() — the primitive on which ReentrantLock.lock(), await(), BlockingQueue.take(), and all of java.util.concurrent are built.

A WAITING thread is using essentially no resources beyond its stack. It will not transition until someone else does something — a notify, a join target finishing, a LockSupport.unpark. If nothing ever unparks it, it sits there forever. That's how silent deadlocks look in a dump: two threads, both WAITING, both holding what the other wants.

TIMED_WAITING

Same idea as WAITING, but with a deadline. The thread will wake itself when the timeout expires even if nothing else happens. Things that produce TIMED_WAITING:

  • Thread.sleep(ms)
  • Object.wait(ms)
  • Thread.join(ms)
  • LockSupport.parkNanos(...), LockSupport.parkUntil(...)
  • BlockingQueue.poll(timeout, unit), Future.get(timeout, unit), etc.

If a thread is reliably stuck in TIMED_WAITING for the duration you specified, that's not a bug. If it stays there past the timeout, it's been re-parked — somebody called wait(1000) in a loop or the queue's still empty.

TERMINATED

run() returned (normally or by exception). The thread is done; it cannot be restarted. t.isAlive() returns false. You can still read its name and ID for log/debug purposes, but the thread itself is finished.

Reading state from your own code

Thread.State is publicly queryable, but the value is a snapshot — it can change between the call and your use of it. In production, you almost never branch on it; you use it for logging and diagnostics. The JVM also exposes ThreadMXBean for full thread dumps, which is what most JMX dashboards display.

Thread t = new Thread(() -> doWork(), "worker");
System.out.println(t.getState());          // NEW
t.start();
System.out.println(t.getState());          // RUNNABLE (or TIMED_WAITING/BLOCKED/etc., racy)
t.join();
System.out.println(t.getState());          // TERMINATED

A worked example: observe every state

The program below creates threads that each get stuck in a different state, then prints what state they're in.

java— editable, runs on the server

What to take from the run:

  • Each of the six states was reachable by code in the same program. NEW and TERMINATED are the boundary cases; the middle four (RUNNABLE, BLOCKED, WAITING, TIMED_WAITING) are the ones you'll see in a real thread dump.
  • The blocked-thread reported BLOCKED because the holder owned the synchronized monitor. Had we used a ReentrantLock instead, the same code path would have reported WAITING (since Lock.lock() parks via LockSupport). The state name tells you what flavour of waiting, not just "this thread is stuck."
  • The waiting-thread would have stayed in WAITING forever if main hadn't called cond.notify(). The WAITING state has no timeout — somebody else has to wake it. This is exactly how a missed notify produces a deadlock that no exception ever reports.
  • The CPU-burning thread reported RUNNABLE whether it was actually running on a core or merely sitting in the run queue waiting for one. The JVM doesn't distinguish "running" from "ready"; the OS does. If you need to know which threads are actually consuming CPU, profile with a sampling profiler — getState() won't tell you.
  • After tRunning.join() returned, its state was TERMINATED. You can still query its name, ID, and state object, but the thread is gone — isAlive() is false and start() would throw. Threads are single-use: when one terminates, you create a new one. (This is the main motivation for ExecutorService — it reuses the same OS thread for many tasks.)

What's next

The next chapter, Java Thread Methods, walks the method surface of Threadsleep, join, yield, interrupt, holdsLock, and the static utilities — with the gotchas for each one.

Practice

Practice

A thread is in state `BLOCKED`. What is it waiting for?