Thread Life Cycle in Java: 6 Thread States With Examples

Learn the thread life cycle in Java with all six Thread.State values, a state diagram, runnable demos, transitions, virtual threads and thread dumps.

State diagram of the Java thread life cycle with NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING and TERMINATED

The thread life cycle in Java is the sequence of states a thread passes through from creation to completion, and the Thread.State enum defines six of them, namely NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING and TERMINATED. A thread is in one of these states at any moment, and getState() tells us which.

We read thread states mainly when something hangs. A thread dump of a frozen app lists the state of every thread, and knowing what each state means tells us whether a thread is waiting for a lock, for another thread or for a timeout.

The following example moves one thread through three states and reads each with getState().

CountDownLatch go = new CountDownLatch(1);
Thread worker = new Thread(() -> {
    try {
        go.await();                               // waits for the signal
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});
Thread.State before = worker.getState();          // NEW
worker.start();
Thread.State parked = awaitState(worker, Thread.State.WAITING);   // WAITING
go.countDown();
worker.join();
Thread.State after = worker.getState();           // TERMINATED

Notice that RUNNING is not in the list, because the JVM reports a running thread and a thread ready to run with the same state. We go through each state with a small runnable demo, list the method calls that cause every transition, look at virtual threads and I/O, and use the states to read a thread dump.

1. Thread Life Cycle States

A Java thread is in one of six states during its life cycle. The names come from the Thread.State enum, so they match what getState(), jstack and IDE debuggers print.

State diagram of the Java thread life cycle with NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING and TERMINATED
A thread leaves RUNNABLE only to wait for a monitor, a signal or a timeout, and it always returns to RUNNABLE before it can end
  • NEW means the thread object exists but start() has not been called.
  • RUNNABLE means the thread is running or ready to run as soon as it gets a CPU core.
  • BLOCKED means the thread waits to enter a synchronized block or method whose monitor another thread holds.
  • WAITING means the thread waits with no time limit until another thread signals it.
  • TIMED_WAITING means the thread waits until a signal arrives or a timeout expires, whichever comes first.
  • TERMINATED means run() has completed, either normally or with an exception.

Each demo uses a small helper that spins until a thread reaches the expected state, so the results do not depend on timing.

static Thread.State awaitState(Thread thread, Thread.State expected) {
    while (thread.getState() != expected && thread.isAlive()) {
        Thread.onSpinWait();
    }
    return thread.getState();
}

1.1. New

As soon as we create a new thread, it is in the NEW state. The thread remains in NEW until the program starts it with start(), and at this point the thread is not alive.

Thread thread = new Thread(() -> {});
Thread.State created = thread.getState();         // NEW
boolean alive = thread.isAlive();                 // false

The same holds for a thread from Thread.ofPlatform().unstarted(task) or Thread.ofVirtual().unstarted(task).

1.2. Runnable

Calling start() puts the thread in the RUNNABLE state. From this point, the operating system scheduler decides when the thread gets a CPU core (also known as dispatching the thread), and thread scheduling is platform-dependent, so the order in which runnable threads execute can differ between runs and machines.

Most operating systems give each thread a small amount of processor time, called a quantum or time slice. When the quantum expires, the thread goes back to the queue of ready threads and another thread gets the core. The JVM does not see this switching, so a thread that is running and a thread that is ready to run both report RUNNABLE.

Thread busy = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        Thread.onSpinWait();                      // keeps the CPU busy
    }
});
busy.start();
Thread.State running = busy.getState();           // RUNNABLE
busy.interrupt();
busy.join();

A thread that waits for I/O, such as a socket read or ServerSocket.accept(), is also RUNNABLE, not BLOCKED. The thread is blocked in the operating system, but the JVM still counts it as executing native code.

ServerSocket server = new ServerSocket(0);
Thread acceptor = new Thread(() -> {
    try (Socket client = server.accept()) {
        // read the request
    } catch (IOException e) {
        // the server socket was closed
    }
});
acceptor.start();
LockSupport.parkNanos(Duration.ofMillis(100).toNanos());
Thread.State duringIo = acceptor.getState();      // RUNNABLE, waiting in accept()
server.close();
acceptor.join();

1.3. Blocked

A RUNNABLE thread moves to the BLOCKED state when it tries to enter a synchronized block or method and another thread holds that monitor. It also becomes BLOCKED when it wakes up from Object.wait() and must take the monitor back. As soon as the monitor is free and the thread acquires it, the thread returns to RUNNABLE.

Object invoiceLock = new Object();
Thread waiter = new Thread(() -> {
    synchronized (invoiceLock) {
        // update the invoice
    }
});
Thread.State whileHeld;
synchronized (invoiceLock) {                      // the current thread holds the monitor
    waiter.start();
    whileHeld = awaitState(waiter, Thread.State.BLOCKED);
}
waiter.join();
Thread.State blockedState = whileHeld;            // BLOCKED

BLOCKED appears only for synchronized monitors. A thread waiting for a ReentrantLock or a Semaphore is parked by LockSupport, so it reports WAITING or TIMED_WAITING instead, which surprises many developers reading a thread dump.

ReentrantLock lock = new ReentrantLock();
Thread contender = new Thread(() -> {
    lock.lock();
    try {
        // update the invoice
    } finally {
        lock.unlock();
    }
});
lock.lock();
contender.start();
Thread.State onLock = awaitState(contender, Thread.State.WAITING);   // WAITING, not BLOCKED
lock.unlock();
contender.join();

1.4. Waiting

A thread enters WAITING when it must wait for another thread to do something first and has no time limit. Three methods lead there, and every higher-level wait, such as CountDownLatch.await() or Future.get(), uses one of them.

  • Object.wait() waits until another thread calls notify() or notifyAll() on the same object, as explained in wait(), notify() and notifyAll().
  • Thread.join() waits until the target thread terminates, as covered in thread join.
  • LockSupport.park() waits until another thread calls LockSupport.unpark() for it, which is how locks, latches and queues block.

The demo in the intro shows WAITING through CountDownLatch.await(). Once the wait is over, the thread returns to RUNNABLE, or to BLOCKED first if it woke up from Object.wait() and the monitor is still taken.

1.5. Timed Waiting

A RUNNABLE thread moves to TIMED_WAITING when it waits with a timeout. We put a thread in this state with the timed versions of the waiting methods.

  • Thread.sleep(millis) pauses the current thread for the given time without releasing any monitor.
  • Object.wait(timeout) and Thread.join(millis) wait for a signal or for the time limit.
  • LockSupport.parkNanos() and LockSupport.parkUntil() park the thread for a duration or until a deadline, which timed lock and latch methods use.
CountDownLatch report = new CountDownLatch(1);
Thread poller = new Thread(() -> {
    try {
        report.await(5, TimeUnit.SECONDS);        // waits at most 5 seconds
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});
poller.start();
Thread.State timed = awaitState(poller, Thread.State.TIMED_WAITING);   // TIMED_WAITING
report.countDown();
poller.join();

Such a thread returns to RUNNABLE when it is signaled or when the time expires. Threads in WAITING and TIMED_WAITING use no CPU, even if a core is free. The differences between pausing and waiting on a monitor are compared in sleep vs wait.

1.6. Terminated

A thread enters the TERMINATED state (sometimes called the dead state) when run() completes, either normally or because an exception escaped it. A Thread object runs only once, so calling start() on it again throws IllegalThreadStateException.

Thread failing = new Thread(() -> {
    throw new IllegalStateException("bad invoice");
});
failing.setUncaughtExceptionHandler((t, e) -> {});   // keeps the console quiet
failing.start();
failing.join();
Thread.State ended = failing.getState();          // TERMINATED
failing.start();                                  // IllegalThreadStateException

There is no way to force a thread into TERMINATED from outside. The method Thread.stop() has thrown UnsupportedOperationException since Java 20, and suspend() and resume() were removed in Java 23, so we ask a thread to finish with interrupt() and let it exit its loop, as shown in how to stop a thread.

2. State Transitions and the Calls That Cause Them

Every arrow in the life cycle diagram belongs to a specific method call or event. The table is the quick reference to use when a thread dump shows an unexpected state.

FromToCaused by
NEWRUNNABLEstart()
RUNNABLEBLOCKEDEntering a synchronized block whose monitor is held, or re-entering after wait()
BLOCKEDRUNNABLEThe monitor becomes free and the thread acquires it
RUNNABLEWAITINGwait(), join(), LockSupport.park(), Lock.lock() under contention
WAITINGRUNNABLEnotify(), notifyAll(), unpark(), the joined thread ends, or an interrupt
RUNNABLETIMED_WAITINGThread.sleep(n), wait(n), join(n), parkNanos(), parkUntil()
TIMED_WAITINGRUNNABLEA signal, the timeout, or an interrupt
RUNNABLETERMINATEDrun() returns or throws

An interrupt wakes a thread from WAITING or TIMED_WAITING, and the waiting method throws InterruptedException so the thread can clean up and exit.

3. States of Virtual Threads

Virtual threads, final since Java 21, use the same six states and the same getState() method. When a virtual thread waits, for example in Future.get() or a blocking socket read, the JVM unmounts it from its carrier platform thread and reports WAITING or TIMED_WAITING, while the carrier runs other virtual threads. Notice the contrast with a platform thread, which stays RUNNABLE during a socket read.

CountDownLatch signal = new CountDownLatch(1);
Thread virtual = Thread.ofVirtual().start(() -> {
    try {
        signal.await();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});
Thread.State virtualWaiting = awaitState(virtual, Thread.State.WAITING);   // WAITING
signal.countDown();
virtual.join();

A virtual thread that cannot enter a synchronized block reports BLOCKED, and since Java 24 (JEP 491) it also unmounts from its carrier while it waits for the monitor. Before Java 24, it stayed pinned to the carrier, which is why older advice told us to replace synchronized with ReentrantLock in virtual thread code.

4. Reading Thread States in a Thread Dump

Say an order service stops answering requests, but the CPU is idle. A thread dump taken with jstack <pid> or jcmd <pid> Thread.print shows the state and stack of every thread, and the states point to the cause.

"http-nio-8080-exec-1" #21 [2378] daemon prio=5 os_prio=0 cpu=0.14ms elapsed=2.40s tid=0x00007f84e40fe4e0 nid=2378 waiting on condition
   java.lang.Thread.State: TIMED_WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@25.0.4.1/Native Method)
	at java.util.concurrent.locks.LockSupport.parkNanos(java.base@25.0.4.1/LockSupport.java:408)
	at com.shop.PriceCache.refresh(PriceCache.java:5)
	- locked <0x00000000a851bcc0> (a com.shop.PriceCache)

"http-nio-8080-exec-2" #22 [2379] daemon prio=5 os_prio=0 cpu=0.08ms elapsed=2.40s tid=0x00007f84e40ff810 nid=2379 waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.shop.PriceCache.refresh(PriceCache.java:5)
	- waiting to lock <0x00000000a851bcc0> (a com.shop.PriceCache)

Request threads in BLOCKED that all wait to lock the same address mean one thread holds the PriceCache monitor for too long. The dump shows the holder with – locked <0x00000000a851bcc0>, here a thread that is parked inside the synchronized method and keeps the monitor while it waits. Many threads in WAITING inside a connection pool point to an exhausted pool instead, and many RUNNABLE threads in socket reads point to a slow downstream service.

5. Deadlock and Starvation

The JVM and the operating system scheduler do their best, but threads can still end up in a deadlock or starve. Both problems show up as threads that stay in BLOCKED or WAITING forever.

A deadlock occurs when thread 1 cannot proceed because it waits for a lock that thread 2 holds, while thread 2 waits for a lock that thread 1 holds. Neither thread ever returns to RUNNABLE, and jstack prints a “Found one Java-level deadlock” section for synchronized monitors and ReentrantLock objects.

Starvation occurs when a thread is RUNNABLE or BLOCKED but rarely gets the CPU or the lock, because other threads keep taking it. A fair lock, new ReentrantLock(true), hands the lock to the longest-waiting thread and prevents starvation at some cost in throughput. More concurrency topics are grouped in the Java concurrency guide.

6. Thread Life Cycle FAQs

Confusing labels in a thread dump, such as RUNNABLE for a thread stuck in a socket read, are the usual starting point for these questions.

6.1. How many thread states are there in Java?

Six. The Thread.State enum defines NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING and TERMINATED. Older textbooks list five states with names like “running” and “dead”, which are not part of the Java API.

6.2. Is there a RUNNING state in Java?

No. The JVM reports a thread that is executing and a thread that is ready to execute with the same RUNNABLE state, because the operating system scheduler switches between them many times per second.

6.3. What is the difference between BLOCKED and WAITING?

A BLOCKED thread wants to enter a synchronized block and waits for the monitor. A WAITING thread has chosen to wait for a signal from another thread, through wait(), join(), park() or a java.util.concurrent lock. A thread in wait() has given up the monitor and needs a notify() before it competes for it again, whereas a BLOCKED thread competes for the monitor as soon as it is free.

6.4. Which state is a thread in during Thread.sleep?

TIMED_WAITING. The thread keeps every monitor it holds while it pauses and returns to RUNNABLE when the time is up or when it is interrupted.

6.5. Can a terminated thread be restarted?

No. Calling start() on a TERMINATED thread throws IllegalThreadStateException. To run the task again, create a new Thread or submit the task to an ExecutorService.

7. Conclusion

A Java thread moves from NEW to RUNNABLE on start(), leaves RUNNABLE only to wait for a monitor, a signal or a timeout, and ends in TERMINATED when run() completes. There is no separate running state, and threads blocked in I/O still report RUNNABLE.

The distinction between BLOCKED for synchronized monitors and WAITING for java.util.concurrent locks is the one that matters most when we read a thread dump. Virtual threads follow the same model, and since Java 24 they no longer pin their carrier while BLOCKED.

8. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. The operating system hide the RUNNABLE and RUNNING states from the Java Virtual Machine (JVM), which sees only the RUNNABLE state. — could not understand this part.

  2. I am finding related to thread lifecycle of java concept I found your article which has detailed information with the excellent diagram. Across your article, I got the complete knowledge of thread life cycle of java. Just after reading your article i came to know that terminated concept of thread lifecycle. Thanks for giving such a wonderful article..

    • As I have highlighted in blue information box: “The operating system hide the RUNNABLE and RUNNING states from the Java Virtual Machine (JVM), which sees only the RUNNABLE state.”

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.