In Java, multithreading is a powerful feature that allows multiple threads to execute concurrently, improving application performance and responsiveness. However, threads in Java don’t always execute; they transition through various states during their lifecycle. Understanding these thread states is essential for effective multithreaded programming.
This tutorial explores the thread states in Java, their significance, transitions, and practical examples.
Thread Lifecycle in Java
The lifecycle of a thread in Java is managed by the java.lang.Thread
class. During its lifecycle, a thread can exist in one of the following states:
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED
These states are defined in the Thread.State
enum, making it easy to monitor a thread’s current status programmatically.
1. NEW State
A thread is in the NEW state when it is created but not yet started. This means the Thread
object has been instantiated, but the start()
method has not been called.
Key Points
- The thread hasn’t begun executing yet.
- It remains in this state until
start()
is invoked.
Example
Thread t = new Thread(() -> System.out.println("Thread running..."));
// t is in NEW state
2. RUNNABLE State
A thread enters the RUNNABLE state after the start()
method is called and the thread is ready to execute. In this state, the thread is considered “ready to run” but may not be actively executing because the CPU scheduler might assign priority to other threads.
Key Points
- The thread is either running or waiting for CPU time.
- This state depends on the JVM and the operating system’s thread scheduler.
Example
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread running: " + i);
}
});
t.start(); // t is now in RUNNABLE state
3. BLOCKED State
A thread enters the BLOCKED state when it tries to access a synchronized block or method that is locked by another thread. It remains in this state until the lock is released.
Key Points
- Indicates a thread is waiting to acquire a lock.
- Prevents simultaneous access to shared resources, ensuring thread safety.
Example
class SharedResource {
synchronized void access() {
System.out.println(Thread.currentThread().getName() + " is accessing the resource");
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
public class BlockedStateExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread t1 = new Thread(resource::access, "Thread 1");
Thread t2 = new Thread(resource::access, "Thread 2");
t1.start();
t2.start(); // t2 will be in BLOCKED state until t1 releases the lock
}
}
4. WAITING State
A thread enters the WAITING state when it waits indefinitely for another thread to perform a specific action. This happens when methods like Object.wait()
or Thread.join()
are called without a timeout.
Key Points
- The thread waits indefinitely until it is notified or interrupted.
- This state is used for thread synchronization.
Example
class WaitingExample {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (Thread.currentThread()) {
try {
Thread.currentThread().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start(); // t1 will enter the WAITING state
}
}
5. TIMED_WAITING State
A thread enters the TIMED_WAITING state when it waits for a specified amount of time. This happens when methods like Thread.sleep()
, Object.wait(timeout)
, or Thread.join(timeout)
are used.
Key Points
- The thread automatically transitions back to RUNNABLE after the specified time elapses.
- Useful for implementing delays or timeouts.
Example
class TimedWaitingExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000); // Thread is in TIMED_WAITING state for 2 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
}
}
6. TERMINATED State
A thread enters the TERMINATED state once its execution is complete. This happens when the run()
method finishes or the thread is explicitly stopped.
Key Points
- The thread is no longer active.
- Once terminated, a thread cannot be restarted.
Example
class TerminatedExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("Thread is running..."));
t1.start();
try {
t1.join(); // Wait for the thread to finish
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread state: " + t1.getState()); // TERMINATED
}
}
Thread State Transitions
The lifecycle and state transitions of a thread can be summarized as:
- NEW → RUNNABLE: When
start()
is called. - RUNNABLE → RUNNING: Scheduled by the OS/thread scheduler.
- RUNNING → WAITING/TIMED_WAITING/BLOCKED: Based on synchronization or waiting conditions.
- RUNNING → TERMINATED: When the thread finishes execution.
Monitoring Thread States
Java provides the getState()
method in the Thread
class to check the current state of a thread.
Example
public class ThreadStateMonitoring {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("Thread running...");
});
System.out.println("State before start: " + t.getState()); // NEW
t.start();
System.out.println("State after start: " + t.getState()); // RUNNABLE
}
}
Conclusion
Understanding thread states in Java is essential for building efficient and bug-free multithreaded applications. By leveraging the appropriate thread states and transitions, developers can ensure proper synchronization, optimal resource utilization, and smoother application performance.
Threads in Java may seem complex initially, but with practice and understanding of their lifecycle, you can master multithreaded programming for robust and scalable software.