Java Full Course: Mastering the Language Thread Life Cycle in Java

Thread Life Cycle in Java

1. Introduction

A thread in Java goes through several states during its lifetime. The java.lang.Thread class defines a State enumeration that represents these states. Understanding the life cycle is crucial for writing correct, efficient, and deadlock‑free concurrent programs.

The thread life cycle consists of the following six states:

  1. NEW
  2. RUNNABLE
  3. BLOCKED
  4. WAITING
  5. TIMED_WAITING
  6. TERMINATED

A thread can transition from one state to another based on method calls (e.g., start(), sleep(), wait(), notify(), join(), etc.) and system scheduling.


2. The Six States Explained

a) NEW (Newly Created)

  • A thread is in the NEW state after it has been created (using new Thread(...)) but before start() is called.
  • At this point, the thread object exists but has not yet started execution.
Thread t = new Thread(() -> System.out.println("Hello")); System.out.println(t.getState()); // NEW

b) RUNNABLE

  • When the start() method is called, the thread moves to the RUNNABLE state.
  • In this state, the thread is ready to run and is waiting for CPU time. The thread may be actually running or waiting for its turn to run.
  • The RUNNABLE state includes both "ready to run" and "running" in the operating system's thread scheduler view.
  • The JVM does not provide a separate state for "running"; it’s considered part of RUNNABLE.
t.start(); System.out.println(t.getState()); // RUNNABLE (if scheduler hasn't started it yet)

c) BLOCKED

  • A thread enters the BLOCKED state when it attempts to acquire a lock (enter a synchronized block/method) that is currently held by another thread.
  • It remains blocked until the lock becomes available, at which point it becomes runnable again.
synchronized (lock) { // thread holding lock } // Another thread that tries to enter the same synchronized block will be BLOCKED

d) WAITING

  • A thread enters the WAITING state when it is waiting indefinitely for another thread to perform a specific action.
  • The transition to WAITING occurs when:
    • Object.wait() without timeout is called.
    • Thread.join() without timeout is called.
    • LockSupport.park() is called.
  • The thread will leave the WAITING state when another thread calls notify() / notifyAll() on the same object, or when the joined thread terminates, or when unpark() is called.
synchronized (obj) { obj.wait(); // thread becomes WAITING }

e) TIMED_WAITING

  • A thread enters the TIMED_WAITING state when it is waiting for a specific amount of time.
  • Transitions to TIMED_WAITING occur when:
    • Thread.sleep(long millis)
    • Object.wait(long timeout)
    • Thread.join(long millis)
    • LockSupport.parkNanos(long nanos) or parkUntil(long deadline)
  • After the time elapses or if it is notified/joined, the thread returns to RUNNABLE.
Thread.sleep(1000); // thread is TIMED_WAITING for 1 second

f) TERMINATED (Dead)

  • A thread enters the TERMINATED state when it has finished execution (the run() method completes normally or abruptly due to an exception).
  • Once terminated, the thread cannot be restarted (calling start() again throws IllegalThreadStateException).
t.join(); // wait for t to finish System.out.println(t.getState()); // TERMINATED

3. State Transition Diagram

Below is a textual representation of the thread state transitions:

NEW | | start() v RUNNABLE | | (OS scheduler) -> (running) | +----------------------+ | | | wait()/join()/ | sleep()/wait(timeout)/ | LockSupport.park() | join(timeout)/LockSupport.parkNanos() v v WAITING TIMED_WAITING | | | notify()/notifyAll() | timeout/notify/ | join finishes | join finishes +----------------------+ | v RUNNABLE | | synchronized block/method | lock not available v BLOCKED | | lock becomes available v RUNNABLE | | run() completes v TERMINATED
Crucial

[!IMPORTANT] BLOCKED and WAITING/TIMED_WAITING are distinct. BLOCKED is about lock contention, whereas WAITING is about explicit waiting for another thread's signal.


4. Methods That Cause State Changes

MethodFrom StateTo StateRemarks
start()NEWRUNNABLEStarts thread execution.
yield()RUNNABLERUNNABLEHints to scheduler to let other threads run. Still RUNNABLE.
sleep(long millis)RUNNABLETIMED_WAITINGAfter sleep finishes → RUNNABLE.
wait()RUNNABLEWAITINGMust be inside synchronized block. Needs notify.
wait(long timeout)RUNNABLETIMED_WAITINGAfter timeout or notify → RUNNABLE.
join()RUNNABLEWAITINGWaits for another thread to finish.
join(long millis)RUNNABLETIMED_WAITINGWaits up to given time.
notify() / notifyAll()(on another thread)WAITING/TIMED_WAITING → RUNNABLEMoves waiting thread(s) to runnable.
Attempt to acquire lock (synchronized)RUNNABLEBLOCKEDWhen lock is held by another thread.
Lock becomes availableBLOCKEDRUNNABLEThe thread competes for CPU.
run() completesRUNNABLETERMINATEDThread ends.

5. Important Methods That Affect State

a) sleep()

  • Causes the current thread to pause for a specified duration.
  • Does not release any locks held by the thread.
  • After the time elapses, the thread moves back to RUNNABLE.
try { Thread.sleep(2000); } catch (InterruptedException e) { // handle interruption }

b) yield()

  • A hint to the scheduler that the current thread is willing to give up its current CPU slice.
  • The thread stays in RUNNABLE state; it may be immediately rescheduled.
  • Not guaranteed; used rarely.

c) join()

  • Allows one thread to wait for the completion of another.
  • If t.join() is called from the main thread, the main thread waits until t finishes.
Thread t = new Thread(() -> { /* work */ }); t.start(); t.join(); // current thread waits until t terminates

d) wait() / notify() / notifyAll()

  • Used for inter‑thread communication.
  • Must be called from a synchronized context.
  • wait() releases the lock and enters WAITING/TIMED_WAITING.
  • notify() wakes up one waiting thread (which then tries to re‑acquire the lock).
  • notifyAll() wakes up all waiting threads.
synchronized (obj) { obj.wait(); // releases lock, waits } // In another thread: synchronized (obj) { obj.notify(); // wakes one waiting thread }

e) interrupt()

  • Sets the interrupt flag of the thread.
  • If the thread is in WAITING/TIMED_WAITING, it throws InterruptedException and clears the interrupt flag.
  • Does not force termination; the thread must handle the interruption.
thread.interrupt();

6. Checking Thread State

You can get the state of a thread using the getState() method. This is useful for debugging.

Thread t = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } }); System.out.println(t.getState()); // NEW t.start(); System.out.println(t.getState()); // RUNNABLE Thread.sleep(100); System.out.println(t.getState()); // TIMED_WAITING (if sleep not finished) t.join(); System.out.println(t.getState()); // TERMINATED

7. Common Pitfalls and Best Practices

PitfallExplanation / Solution
Assuming yield() gives up CPUyield() is just a hint; the thread may continue running.
Not handling InterruptedExceptionMethods like sleep(), wait(), join() throw InterruptedException. Always handle it (restore interrupt or exit gracefully).
Calling wait() without synchronizationThrows IllegalMonitorStateException. Must be inside a synchronized block on the same object.
Using Thread.stop() (deprecated)Unsafe; use a flag and check it periodically.
Not releasing locks when waitingwait() releases the lock; sleep() does not. Understand the difference.
Deadlocks due to lock orderingThreads can be BLOCKED forever if locks are acquired in inconsistent order.
Tip

[!TIP] Best Practices:

  • Prefer java.util.concurrent utilities (CountDownLatch, BlockingQueue, Executors) over low‑level wait/notify.
  • Use Thread.join() to wait for termination.
  • Always name threads for easier debugging.
  • Never start a thread in a constructor (it can expose partially constructed object).
  • Use interrupt() to politely ask a thread to stop, and design your thread to respond to interruptions.

8. Complete Example: Observing Thread States

public class ThreadLifeCycleDemo { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread t = new Thread(() -> { // BLOCKED state demonstration synchronized (lock) { System.out.println("Thread acquired lock"); try { Thread.sleep(1000); } catch (InterruptedException e) {} } }); Thread mainThread = Thread.currentThread(); // 1. NEW state System.out.println("After creation: " + t.getState()); // 2. RUNNABLE after start t.start(); System.out.println("After start: " + t.getState()); // 3. TIMED_WAITING when sleep inside thread Thread.sleep(100); System.out.println("During sleep (in thread): " + t.getState()); // 4. BLOCKED: main thread holds the lock, t will try to acquire it synchronized (lock) { Thread blocker = new Thread(() -> { synchronized (lock) { System.out.println("Blocker acquired lock"); } }); blocker.start(); Thread.sleep(100); System.out.println("Blocker waiting for lock: " + blocker.getState()); // BLOCKED } t.join(); System.out.println("After completion: " + t.getState()); // TERMINATED } }

Possible output (order may vary):

After creation: NEW After start: RUNNABLE During sleep (in thread): TIMED_WAITING Blocker waiting for lock: BLOCKED Thread acquired lock After completion: TERMINATED

9. Key Points to Remember

  • A thread goes through NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED.
  • RUNNABLE encompasses both ready and running states.
  • BLOCKED is for waiting to enter a synchronized block/method.
  • WAITING and TIMED_WAITING are for indefinite or time‑limited waiting (e.g., wait(), join(), sleep()).
  • sleep() does not release locks; wait() does.
  • yield() is a hint; the thread may not yield.
  • Use getState() for monitoring (mostly debugging).
  • Modern concurrent programming favors higher‑level utilities (java.util.concurrent) over manual state management.
Hi! Need help with studies? 👋
AI