Understanding Java Thread States and Their Transitions with Example Code
This article explains the six Java thread states defined in java.lang.Thread.State, describes the meaning of each state, illustrates the possible state transitions, and provides three runnable code examples that demonstrate NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING and TERMINATED states along with their console outputs.
When a thread is created and started in Java, it does not immediately enter the running state; instead, it moves through a series of well‑defined states represented by the java.lang.Thread.State enum. The six possible states are NEW , RUNNABLE , BLOCKED , WAITING , TIMED_WAITING and TERMINATED , each with a specific meaning.
The table below summarizes each state and its description:
Thread State
Specific Meaning
NEW
A thread that has been created but not yet started (the
start()method has not been called).
RUNNABLE
The thread is eligible to run and is waiting for CPU scheduling; it may be executing or ready to execute.
BLOCKED
The thread is trying to acquire an object lock that is held by another thread.
WAITING
The thread is waiting indefinitely for another thread to perform a specific action (e.g.,
Object.wait()or
Thread.join()).
TIMED_WAITING
The thread is waiting for a specified period (e.g.,
Thread.sleep(),
Object.wait(long),
Thread.join(long)).
TERMINATED
The thread has completed execution and cannot be started again.
Example 1 – Demonstrating TIMED_WAITING
This example prints the thread state sequence NEW → RUNNABLE → TIMED_WAITING → RUNNABLE → TERMINATED using an anonymous inner class and lambda expression.
public class ThreadStateDemo01 {
public static void main(String[] args) throws InterruptedException {
// Define an inner thread
Thread thread = new Thread(() -> {
System.out.println("2. After thread.start(), state: " + Thread.currentThread().getState());
try {
// Sleep 100 ms
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4. After Thread.sleep() completes, state: " + Thread.currentThread().getState());
});
// State before start
System.out.println("1. Before start, state: " + thread.getState());
thread.start();
// Sleep 50 ms so the thread is still sleeping
Thread.sleep(50);
System.out.println("3. While sleeping, state: " + thread.getState());
// Wait for thread to finish
Thread.sleep(100);
System.out.println("5. After execution, state: " + thread.getState());
}
}Console output:
1. Before start, state: NEW
2. After thread.start(), state: RUNNABLE
3. While sleeping, state: TIMED_WAITING
4. After Thread.sleep() completes, state: RUNNABLE
5. After execution, state: TERMINATEDExample 2 – Demonstrating WAITING
This example shows the transition NEW → RUNNABLE → WAITING → RUNNABLE → TERMINATED by using Object.wait() and Object.notify() .
public class ThreadStateDemo02 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread thread1 = new Thread(() -> {
System.out.println("2. After thread.start(), state: " + Thread.currentThread().getState());
synchronized (obj) {
try {
Thread.sleep(100);
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4. After notify(), state: " + Thread.currentThread().getState());
});
System.out.println("1. Before start, state: " + thread1.getState());
thread1.start();
Thread.sleep(150);
System.out.println("3. While waiting, state: " + thread1.getState());
// Another thread to notify
new Thread(() -> {
synchronized (obj) {
obj.notify();
}
}).start();
Thread.sleep(10);
System.out.println("5. After execution, state: " + thread1.getState());
}
}Console output:
1. Before start, state: NEW
2. After thread.start(), state: RUNNABLE
3. While waiting, state: WAITING
4. After notify(), state: RUNNABLE
5. After execution, state: TERMINATEDExample 3 – Demonstrating BLOCKED
This example illustrates the sequence NEW → RUNNABLE → BLOCKED → RUNNABLE → TERMINATED by having one thread hold a lock while another attempts to acquire it.
public class ThreadStateDemo03 {
public static void main(String[] args) throws InterruptedException {
Object obj2 = new Object();
// Thread that holds the lock and then waits
new Thread(() -> {
synchronized (obj2) {
try {
Thread.sleep(100);
obj2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Target thread that will be blocked
Thread thread = new Thread(() -> {
System.out.println("2. After thread.start(), state: " + Thread.currentThread().getState());
synchronized (obj2) {
try {
Thread.sleep(100);
obj2.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4. After lock released, state: " + Thread.currentThread().getState());
});
System.out.println("1. Before start, state: " + thread.getState());
thread.start();
Thread.sleep(50);
System.out.println("3. While waiting for lock, state: " + thread.getState());
Thread.sleep(300);
System.out.println("5. After execution, state: " + thread.getState());
}
}Console output:
1. Before start, state: NEW
2. After thread.start(), state: RUNNABLE
3. While waiting for lock, state: BLOCKED
4. After lock released, state: RUNNABLE
5. After execution, state: TERMINATEDThrough these three demonstrations, the article confirms that the Java thread states and their transitions described at the beginning are accurate.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.