Thread Lifecycle

The six Java thread states and what moves a thread between them, BLOCKED versus WAITING in a thread dump, daemon threads, and how to stop a thread safely in Java and Python.

Overview

A thread is created, started, runs, may wait for locks or for other threads, and finally ends. Knowing those stages lets you read a thread dump, explain why a program hangs or refuses to exit, and stop threads without corrupting data.

Java names the stages exactly: Thread.State has six values, and getState() returns one of them. The docs stress that these are JVM states, not operating-system states, and that getState() is for monitoring, not for synchronization. Python has no state enum; a thread is either not started, alive (is_alive()), or finished.

The six states

  • NEW — Created, start() not called yet — new Thread(r)
  • RUNNABLE — Executing in the JVM, possibly waiting for a CPU or other OS resources — Running code, or blocked in some native I/O
  • BLOCKED — Waiting for a monitor lock to enter a synchronized block or method, or to re-enter one after Object.wait() — Another thread holds the monitor
  • WAITING — Waiting with no timeout for another thread to act — Object.wait(), Thread.join(), LockSupport.park()
  • TIMED_WAITING — The same, with a timeout — Thread.sleep(ms), wait(ms), join(ms), parkNanos, parkUntil
  • TERMINATED — Finished: run() returned or threw — Done

Interviewers often probe two details: first, a thread waiting on a ReentrantLock shows as WAITING (or TIMED_WAITING), not BLOCKED, because those locks park the thread, while BLOCKED means an intrinsic synchronized monitor specifically. Second, a thread can be started only once: calling start() again throws IllegalThreadStateException in Java and RuntimeError in Python, even after it has finished.

The same lifecycle in Python

Python's threading.Thread has the same shape without the names: is_alive() is true from just before run() starts until just after it ends. join(timeout) always returns None, so the docs say to call is_alive() afterwards to learn whether the join timed out.

Daemon threads

A daemon thread does not keep the program alive. Java starts the shutdown sequence when every started non-daemon thread has ended; Python exits when no alive non-daemon threads are left. A new thread inherits the daemon flag of the thread that creates it, and the flag must be set before start().

  • Set it — setDaemon(true) before start(); throws IllegalThreadStateException if the thread is alive — daemon=True before start(); RuntimeError otherwise
  • Default — Inherited from the creating thread; main is non-daemon — Inherited; the main thread is non-daemon
  • At exit — The JVM can exit while daemons still run — Daemons are stopped abruptly; open files and transactions may not be cleaned up
  • Virtual threads (Java 21) — Always daemon; cannot be changed — not applicable

Use daemons for work that is safe to abandon: a cache refresher, a heartbeat, a metrics flusher that can lose its last batch. Never for anything that must finish, such as writing a file or committing a transaction; for those, use a normal thread and a stop signal.

What you will be able to do

  • Draw the six Java states and name the call that causes each transition.
  • Tell BLOCKED from WAITING in a thread dump and say what each suggests.
  • Use join with a timeout correctly in both Java and Python.
  • Decide when a daemon thread is safe.
  • Stop a thread cooperatively with interruption or an event.

Interview questions

  • What are the six thread states in Java?
  • What is the difference between BLOCKED and WAITING?
  • What is a daemon thread, and when do you use one?
  • How do you stop a thread safely?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio