Software Development

Thread Lifecycle in Java | Developer.com


Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Threads are fundamental units of execution in Java that allow for concurrent and parallel execution of tasks. Understanding the thread lifecycle is essential for writing efficient and robust multi-threaded applications. In this programming tutorial, we will dive deep into the various stages of the thread lifecycle in Java, accompanied by code examples.

Overview of the Java Thread Lifecycle

There are several distinct states involved in the life cycle of a thread as shown in the following diagram:

Java Thread lifecycle

The rest of this article will cover each of the following thread states in more detail:

Read: Best Online Courses to Learn Java

Java Thread State: New

At this stage, a thread is created but not yet started. You can create a new thread by instantiating the Thread class or implementing the Runnable interface and passing it to a Thread instance like so:

public class NewThreadExample {
  public static void main(String[] args) {
    Thread newThread = new Thread(() -> {
      System.out.println("New thread is executing.");
    });
    System.out.println("Thread state: " + newThread.getState()); 
    // Output: Thread state: NEW
  }
}

Active (Runnable & Running)

When the start() method is called on a thread, it becomes Active by transitioning from the New state to the Runnable state. A thread in this state is eligible to run, but the actual execution depends on the scheduler. Threads in the Runnable state can be executing concurrently with other threads. Here is some example code:

public class RunnableThreadExample {
  public static void main(String[] args) {
    Thread runnableThread = new Thread(() -> {
      System.out.println("Runnable thread is executing.");
    });
    runnableThread.start();
    System.out.println("Thread state: " + runnableThread.getState()); 
    // Output: Thread state: RUNNABLE
  }
}

A thread in the Runnable state can transition to the Running state when the scheduler allocates processor time to it. The thread’s run() method is executed, and it performs its designated tasks.

public class RunningThreadExample {
  public static void main(String[] args) {
    Thread runningThread = new Thread(() -> {
      System.out.println("Running thread is executing.");
    });
    runningThread.start();
    // Output: Running thread is executing.
  }
}

Read: Top Java Frameworks

Blocked/Waiting

A thread can enter the Blocked or Waiting state for various reasons. One common scenario is when a thread is waiting for a lock held by another thread. A second scenario is when a thread is explicitly paused using methods like Thread.sleep() or Object.wait().

public class BlockedThreadExample {
  public static void main(String[] args) {
    Object lock = new Object();
    Thread blockedThread = new Thread(() -> {
      synchronized (lock) {
        try {
          Thread.sleep(2000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    });
    
    Thread waitingThread = new Thread(() -> {
      synchronized (lock) {
        try {
          lock.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    });
    
    blockedThread.start();
    waitingThread.start();
    
    System.out.println("Blocked thread state: " + blockedThread.getState());
    System.out.println("Waiting thread state: " + waitingThread.getState());
    /* 
    Output:
    Blocked thread state: TIMED_WAITING
    Waiting thread state: BLOCKED
    */
  }
}

Timed Waiting

Threads in the Blocked/Waiting state can enter the Timed Waiting state when they are paused for a specified amount of time. This can happen when using methods like Thread.sleep() or Object.wait(timeout).

public class TimedWaitingThreadExample {
  public static void main(String[] args) {
    Thread timedWaitingThread = new Thread(() -> {
      try {
        Thread.sleep(3000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
    timedWaitingThread.start();
    
    System.out.println("Thread state before sleep: " + timedWaitingThread.getState());
    
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    
    System.out.println("Thread state after sleep: " + timedWaitingThread.getState());
    /* 
    Output:
    Thread state before sleep: RUNNABLE
    Thread state after sleep: TIMED_WAITING
    */
  }
}

Terminated/Dead

A thread enters the Terminated state when its run() method completes execution or when an unhandled exception occurs. A terminated thread cannot be restarted. You can check if a thread has terminated using the Thread.isAlive() method.

public class TerminatedThreadExample {
  public static void main(String[] args) {
    Thread terminatedThread = new Thread(() -> {
      System.out.println("Thread is executing.");
    });
    terminatedThread.start();
    
    try {
      terminatedThread.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    
    System.out.println("Is thread terminated? " + !terminatedThread.isAlive());
    /* 
    Output:
    Thread is executing.
    Is thread terminated? true
    */
  }
}

Final Thoughts on the Java Thread Lifecycle

In this article, we explored the five main stages of the thread lifecycle: New, Active (Runnable/Running), Blocked/Waiting, Timed Waiting, and Terminated. Each stage plays a crucial role in determining how threads interact with each other and with the underlying system resources.

Understanding the thread lifecycle is crucial for writing efficient and bug-free multi-threaded applications. Java’s thread management allows developers to harness the power of concurrency to build robust software systems. By comprehending the various stages of the thread lifecycle, developers can create applications that take full advantage of multi-core processors and provide responsive user experiences.

Remember to use synchronization mechanisms like synchronized blocks and methods, as well as higher-level concurrency utilities provided by Java’s java.util.concurrent package, to ensure proper coordination between threads and prevent issues like race conditions and deadlocks.

By mastering the thread lifecycle and incorporating best practices for multi-threaded programming, developers can create Java applications that excel in performance, responsiveness, and scalability.

Next Steps

Now that you have a better understanding of the Java Thread lifecycle and how the different states of the lifecycle operate, we recommend you check out some of our other tutorials on Java threading, multithreading, and concurrency. We highlight a few below: