CodeGym /Courses /JAVA 25 SELF /Thread states and lifecycle

Thread states and lifecycle

JAVA 25 SELF
Level 51 , Lesson 2
Available

1. Core thread states in Java

In Java, a thread is not just “running” or “stopped.” It has a full lifecycle, and at each stage a thread behaves differently. Understanding these stages is the key to writing stable multithreaded applications and to debugging strange “hangs” and “unexpected terminations.”

What states are there?

Java defines the following main thread states (they are listed in the enum Thread.State):

State Description
NEW
The thread is created but not yet started (new Thread(...), but start() hasn’t been called)
RUNNABLE
The thread is ready to run or is running right now
BLOCKED
The thread is waiting for a monitor to be released (blocked on a synchronized block)
WAITING
The thread is waiting for another thread to “wake it up” (for example, via Object.wait())
TIMED_WAITING
The thread is waiting with a timeout (for example, Thread.sleep(1000), wait(1000), join(1000))
TERMINATED
The thread has finished execution

Fun fact:
In older books and articles you may encounter different names or slightly different diagrams. But since Java 5+, these states are considered the standard.

Visual diagram of the thread lifecycle

stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start()
    RUNNABLE --> BLOCKED: attempt to enter synchronized, but monitor is busy
    BLOCKED --> RUNNABLE: monitor released
    RUNNABLE --> WAITING: wait(), join()
    RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
    WAITING --> RUNNABLE: notify()/notifyAll(), join() completed
    TIMED_WAITING --> RUNNABLE: timeout expired / notify()/notifyAll()
    RUNNABLE --> TERMINATED: run() finished
    WAITING --> TERMINATED: run() finished (rare)
    TIMED_WAITING --> TERMINATED: run() finished (rare)

2. Thread management methods

Sleeping: Thread.sleep(long millis)

Sometimes a thread needs to “sleep” so it doesn’t interfere with others or while it waits for an event. The Thread.sleep(ms) method puts the thread into the

TIMED_WAITING
state for the specified number of milliseconds.

System.out.println("Thread is going to sleep for 2 seconds...");
Thread.sleep(2000); // Sleep for 2 seconds
System.out.println("Thread woke up!");
  • After sleeping, the thread returns to the
    RUNNABLE
    state (ready to work).
  • If the thread is interrupted while sleeping, an InterruptedException is thrown.

Waiting for another thread to finish: join()

Sometimes you need not just to start a thread but to wait until it finishes. For this, Java provides the join() method:

Thread t = new Thread(() -> {
    System.out.println("Working...");
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    System.out.println("Done!");
});
t.start();
System.out.println("Waiting for thread t to finish...");
t.join(); // The current thread (e.g., main) waits for t
System.out.println("Thread t has finished!");

Here, the main thread starts waiting as soon as join() is called. All this time it is in the

WAITING
state until thread t finishes execution. There is also a timed variant — join(long millis). In that case the thread waits for a limited time, and its state will be
TIMED_WAITING
.

Interrupting a thread: interrupt()

Sometimes you need to politely ask a thread to finish its work, for example, if the user clicked “Cancel.” For this, use the interrupt() method:

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // Working...
    }
    System.out.println("Thread finished due to interruption!");
});
t.start();
// ... after some time:
t.interrupt(); // Signal to the thread: "time to stop"

It’s important to understand that calling interrupt() doesn’t kill a thread instantly. It only sets a special flag. The thread itself should periodically check this flag via isInterrupted() and terminate on its own. If the thread is sleeping (sleep()) or waiting (wait()) at that moment, it will not just see the flag — it will immediately get an InterruptedException. This is the “proper” way to stop threads in Java: the program doesn’t forcefully terminate them, it notifies them that it’s time to finish.

3. Examples of transitions between states

Let’s look at examples of how a thread “travels” through its states.

Example 1:
NEW
RUNNABLE
TERMINATED

Thread t = new Thread(() -> System.out.println("Hello!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (or TERMINATED if the thread is very fast)
t.join();
System.out.println(t.getState()); // TERMINATED

Example 2:
RUNNABLE
TIMED_WAITING
RUNNABLE
TERMINATED

Thread t = new Thread(() -> {
    try {
        System.out.println("Going to sleep...");
        Thread.sleep(1000); // TIMED_WAITING
        System.out.println("Woke up!");
    } catch (InterruptedException e) {
        System.out.println("Thread interrupted!");
    }
});
t.start();

Example 3:
RUNNABLE
WAITING
with join()

Thread t1 = new Thread(() -> {
    try { Thread.sleep(500); } catch (InterruptedException ignored) {}
    System.out.println("t1 finished");
});
Thread t2 = new Thread(() -> {
    try {
        t1.join(); // t2 waits for t1, is in WAITING
        System.out.println("t2 waited for t1");
    } catch (InterruptedException ignored) {}
});
t1.start();
t2.start();

Example 4:
BLOCKED

Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        System.out.println("t1 exited the synchronized block");
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2 entered synchronized");
    }
});
t1.start();
Thread.sleep(100); // Let t1 acquire the lock
t2.start();
Thread.sleep(100); // Let t2 try to enter synchronized
System.out.println("t2 state: " + t2.getState()); // BLOCKED

4. How to get a thread’s state? Methods isAlive() and getState()

  • isAlive() — returns true if the thread has been started and hasn’t finished yet (
    TERMINATED
    means false).
  • getState() — returns the current state of the thread (a value from the Thread.State enum).
Thread t = new Thread(() -> {});
System.out.println(t.isAlive()); // false (NEW)
t.start();
System.out.println(t.isAlive()); // true (RUNNABLE/WAITING/...)
t.join();
System.out.println(t.isAlive()); // false (TERMINATED)

5. Why you can’t “kill” a thread directly and other practical advice

There is no “kill” method!

Java doesn’t have a method that lets you “kill” a thread on command. Why? Because it’s unsafe: if a thread holds some resource (a file, a connection, a lock), forcefully destroying it may leave the system in an inconsistent state.

Deprecated methods: stop(), suspend(), resume()

In very old versions of Java, there were methods stop(), suspend(), resume(). They are now marked as @Deprecated and using them is strongly discouraged. Why?

  • stop() can kill a thread at any moment, leaving data in an inconsistent state.
  • suspend() can “freeze” a thread that holds a lock, and then the entire program can hang.
  • resume() may not be able to “unfreeze” a thread if it has already finished.

Modern approach:
A thread should terminate gracefully on its own by responding to the interrupt flag (isInterrupted()) or other signals.

Best practices

  • Do not call methods that are marked as deprecated.
  • Use the interrupt flag to stop a thread (interrupt() and checking isInterrupted()).
  • Monitor thread states during debugging — this will help you find hangs and deadlocks.
  • Don’t forget about join() if you need to wait for a thread to finish.

6. Common mistakes when working with the thread lifecycle

Mistake #1: Starting a thread again.
In Java, you can start a thread only once. If you call start() a second time, you’ll get an IllegalThreadStateException. If you need to repeat a task, create a new Thread object.

Thread t = new Thread(() -> {});
t.start();
t.start(); // Will throw an exception!

Mistake #2: Confusing run() and start().
Calling run() directly does not run the code in a new thread — it executes in the current one (for example, in main). Only start() actually launches a new thread.

Mistake #3: Not handling InterruptedException.
If a thread is sleeping or waiting and it gets interrupted, an InterruptedException will be thrown. If you ignore it, the thread may “sleep forever” or terminate unexpectedly.

Mistake #4: Not checking the thread state.
Sometimes a program hangs because one thread is waiting for another that has already finished or will never start. Use getState() and isAlive() for diagnostics.

Mistake #5: Using deprecated thread-control methods.
Methods stop(), suspend(), resume() are harmful. Don’t use them, even if you really want to “fix everything quickly.”

1
Task
JAVA 25 SELF, level 51, lesson 2
Locked
Space Mission Completion Check 🛰️
Space Mission Completion Check 🛰️
1
Task
JAVA 25 SELF, level 51, lesson 2
Locked
Synchronization of two workers 🤝
Synchronization of two workers 🤝
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION