DEV Community

Cover image for Java Thread Life Cycles
Ehis Edemakhiota
Ehis Edemakhiota

Posted on • Updated on

Java Thread Life Cycles

Introduction

A thread is the smallest unit of processing. A thread can be in different states in a cycle leading from its creation to its termination.
This article explores the different states in the Java thread life cycle. This is the second article of a concurrency series. The first article introduced the concepts of Multi-threading, Concurrency, Asynchronous Programming and Parallel Programming. You might want to take a look here.

Definition of Terms

  • Process: A process is an instance of a program that is currently being executed by the processor. Different instances of the same program can run on a computer as different processes. This means that when I run MS Word on my computer, for instance, it runs as a process. I can run different instances of MS Word on my computer. For each new instance of MS Word, the processor spins up a new process. Each process has its own unique stack, memory and data.
  • Thread: A thread is a subset of a process. A process starts with one thread called the main thread. The main thread can branch into other threads. A process that contains only the main thread is called a single-threaded process. A process that contains more than one thread is called a multi-threaded process. All threads within a process all share the same code-segment, data-segment, registers, stack and program counter.

Lifecycle of threads in Java

Java supports multithreading. A Java program can be designed in such a way that its different parts run on different threads. The execution of a Java program begins from the main method. The main method is executed on a special thread called the main thread, which is created by the Java Virtual Machine(JVM). Other threads are created as off-shoots of the main thread.

In Java, threads belong to the class- java.lang.Thread. You can create a Java thread by implementing the Runnable interface as shown in the following block:

/*
 * CREATING A JAVA THREAD BY IMPLEMENTING THE RUNNABLE INTERFACE
*/

public class SimpleThreadTwo implements Runnable{

    //the run method contains code that the thread will execute
    @Override
    public void run() {
        System.out.println("A simple thread has been created by implementing the Runnable interface");
    }
}
Enter fullscreen mode Exit fullscreen mode

You can also create a Java thread by extending the Thread class as shown in the following block:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
* CREATING A JAVA THREAD BY EXTENDING THE THREAD CLASS
* */


public class SimpleThread extends Thread {

    // the run method contains code that the thread will execute
    @Override
    public void run() {
        System.out.println("A simple thread has been created by extending the Thread class");
    }
}
Enter fullscreen mode Exit fullscreen mode

To execute both threads defined above, you create an ExecutorService. The ExecutorService provides a thread pool that allows you to execute threads using the execute method as shown below:

//creating an executor service that executes threads
class Executor{
    public static void main(String[] args) {
        SimpleThread simpleThread = new SimpleThread();
        SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();

        // create the thread pool
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(simpleThread);
        executorService.execute(simpleThreadTwo);

        //the shutdown method notifies the executor service to stop accepting new tasks
        //but the executor service still continues to run tasks that have already been accepted
        executorService.shutdown();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

A simple thread has be created by extending the Thread class
A simple thread has been created by implementing the Runnable interface

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode

You can also execute threads using the Thread class by invoking the start method. To execute a thread created by extending the Thread class, you have to invoke the start method on the thread object as follows:

Take note that you have to first create a class that extends java’s Thread class. (Using the SimpleThread class created above)

public class MyThread {
    public static void main(String[] args) {
        SimpleThread simpleThread = new SimpleThread();
        simpleThread.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

A simple thread has be created by extending the Thread class

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode

To execute a thread created by implementing the Runnable interface, you have to pass in a Runnable as an argument to the Thread constructor. This is demonstrated in the code block below:

Take note that you have to first create a class that implements java’s Runnable class before you can do this.
(Using the SimpleThreadTwo class created above.)

public class MyThread {
    public static void main(String[] args) {
        SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();

        //passing the Runnable object as an argument into the Thread class
        Thread thread = new Thread(simpleThreadTwo);
        thread.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

A simple thread has been created by implementing the Runnable interface

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode

It is important to highlight that creating a thread by implementing the Runnable interface is more encouraged than creating threads using the Thread class.

The java.lang.Thread class has a static enum property- State. The State property defines the potential state of a Java thread. During its life cycle, a java thread can belong to any of the following states:

  • NEW
  • RUNNABLE
  • WAITING
  • TIMED-WAITING
  • BLOCKED
  • TERMINATED

The following is a diagrammatic representation of the java thread lifecycle:

Java Thread Life Cycle
Source: baeldung.com

The following section describes these thread states in more detail:

  • NEW: When a thread is created, it is in the NEW state. A thread remains in the new state until its start method is called.
public class MyThread {
public static void main(String[] args) {
            SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();
            //passing the Runnable object as an argument into the Thread constructor
            Thread thread = new Thread(simpleThreadTwo);
            System.out.printf("The current state of thread is %s", thread.getState());
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

The current state of thread is NEW

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode
  • RUNNABLE: A thread is in the RUNNABLE state when the processor executes its start method. Threads in the RUNNABLE state are either READY to run or are already RUNNING.

When a thread transitions to the RUNNABLE state, the thread starts first in the READY state. At the READY state, the thread has not been assigned processor time also called a quantum or a time slice. When a thread is assigned a time slice, the thread is said to be dispatched. A newly dispatched thread joins a queue of other dispatched threads that are also waiting to run. The operating system uses a Thread Scheduler to determine which thread to run.

When a thread starts to run, it transitions into its RUNNING state. In the RUNNING state, the task defined within the run method of the threads is executed.

public class MyThread {
    public static void main(String[] args) {
            SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();
            //passing the Runnable object as an argument into the Thread constructor
            Thread thread = new Thread(simpleThreadTwo);
            // the start method executes the code defined in the run method of the thread
        thread.start();
            System.out.printf("%nThe current state of thread is %s%n", thread.getState());
    }
}
Enter fullscreen mode Exit fullscreen mode
  • WAITING: A thread enters the WAITING state while it waits for another thread to perform a task. A running thread can be placed on wait when one of the following methods is invoked:
    • wait: The wait method causes an object to give up its timeslice and to release the monitor lock on a synchronized object. A monitor lock is a contract that gives a thread sole access to an object. The wait method causes the object to give up processor time and to transition from the RUNNING state to the WAITING state. A thread in waiting can transition back into execution when the thread that currently has the monitor lock calls the notify or notifyAll method.
    • LockSupport.park: The LockSupport.park method disables a thread unless a permit is available. A permit is a permission for a thread to continue execution. When the park method is called on the current thread, the thread remains in the WAITING state. When some other thread invokes the unpark method with the parked thread as the target, the parked thread goes back into the RUNNING state.
    • join: The join method allows a thread to remain in the WAITING state until another thread completes execution.
public class WaitingStateDemo implements Runnable{
    public static Thread firstThread;

    public static void main(String[] args) {
            firstThread = new Thread(new WaitingStateDemo());
            firstThread.start();
    }
    @Override
    public void run() {
            Thread secondThread = new Thread(new WaitingStateDemoInner());
            secondThread.start();
         try {
                secondThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }


    static class WaitingStateDemoInner implements  Runnable{

            @Override
            public void run() {
                System.out.println("The current state of thread 2 is " + firstThread.getState());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

The current state of thread 2 is WAITING

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode

Execution starts from the main method in the above program.

  • First, we create firstThread and invoke its run method by calling start.
  • In the run method of firstThread, we create secondThread and start it.
  • While the processing of firstThread continues, we call the join method on secondThread. The join method puts firstThread in the WAITING state until secondThread has finished its execution.
  • Since firstThread is waiting for secondThread to complete, you can get the state of firstThread from secondThread.

To better understand the WAITING state, consider this scenario:

You have an onsite interview for a position that has the prospects of an amazing salary. On your arrival for the interview, the secretary tells you to wait until the hiring manager notifies you to come in. At this point, you are in the WAITING state. You cannot proceed to the interview until the hiring manager notifies you to come in.

  • TIMED-WAITING: A thread enters the TIMED-WAITING state when it is waiting for another thread to perform a task within a specified time interval. A thread can be put in the TIMED-WAITING state by calling one of the following methods:
    • thread.sleep(long millis)
    • thread.wait(int timeout) or thread.wait(int timeout, int nanos)
    • thread.join(long millis)
    • LockSupport.parkNanos
    • LockSupport.parkUntil

You can demonstrate the TIMED-WAITING state by creating a thread from the main thread. You can call the sleep method on the offshoot thread. This is shown as follows:

public class TimedWaitingStateDemo {
    public static void main(String[] args) {
            Thread newThread = new Thread(new DemoThread());
         newThread.start();

        // the following sleep gives the ThreadScheduler enough time to start processing newThread
         try {
                Thread.sleep(1000);
             System.out.println("The current state of newThread is: " + newThread.getState());
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             System.err.println("Thread interrupted " + e);
         }
    }
}

class DemoThread implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("Thread interrupted " + e);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

A thread in the TIMED-WAITING state transitions back to the RUNNABLE **state when the task it is waiting for completes execution or when the specified time interval elapses. To better understand the **TIMED-WAITING state, consider the following scenario:

You and a friend in another city get tickets to go see a music concert organized in your city. On the day of the concert, you agree with your friend to wait for him until 2 pm. If he does not arrive on/before 2 pm, you can go to the concert alone. Here, you are placed in the TIMED-WAITING state. You can choose to proceed with going to the concert after the specified time (until 2 pm) has elapsed.

  • BLOCKED: A thread transitions into the BLOCKED state when one of the following happens during program execution:

    • When the thread is waiting for an I/O operation to complete.
    • When the thread is waiting to gain access to the monitor lock on a synchronized object. A monitor lock is a contract that gives a thread sole access to a synchronized object. A synchronized object is an object that is designed to be accessed by only one thread at a time.

To understand the BLOCKED state, consider this scenario:

You have a meeting with a friend but as you step out of your house, it begins to rain. At this point, you are in a BLOCKED state. You are blocked from meeting with your friend until the rain stops.

  • TERMINATED: A thread enters the TERMINATED state ( sometimes called the DEAD state) when one of the following happens:
    • when it completely executes its task ( as defined in the run method).
    • when it terminates due to an error.
public class TerminatedStateDemo implements Runnable{
    @Override
    public void run() {
            System.out.println("This demo is to show the terminated state");
}

    public static void main(String[] args) throws InterruptedException {
         Thread newThread = new Thread(new TerminatedStateDemo());
         newThread.start();
        // this sleep is intended to enable newThread execute completely
         Thread.sleep(5000);
         System.out.println("The current state of the new thread is : " + newThread.getState());
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

This demo is to show the terminated state
The current state of the new thread is: TERMINATED

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode

Thread Priorities and Thread Scheduling

The number of services assigned to a given thread is referred to as its priority. Every Java thread has a thread priority. The order in which threads are scheduled is determined by each thread’s thread-priority. Threads with a higher thread-priority are executed before threads with a lower thread priority. In Java, thread-priorities are scaled on a scale of 1 to 10.

  • 1 is the lowest priority.
  • 5 is the standard priority.
  • 10 is the highest priority.

The priority of the main thread is set to 5 by default. Thread priority is a transferred property. Since every java thread is created off the main thread, every thread starts with a priority of 5. The priority of a thread can be programmed using the setPriority method. The following enums represent thread priorities:

  • Thread.MIN_PRIORITY represents the lowest priority.
  • Thread.NORM_PRIORITY represents the standard priority.
  • Thread.MAX_PRIORITY represents the highest priority.

Below is a program to understand the Thread-Priority:

public class ThreadPriority implements Runnable{
    @Override
    public void run() {
        System.out.println("The thread priority of the running thread is " + Thread.currentThread().getPriority());
    }

    public static void main(String[] args) {
        Thread firstThread = new Thread(new ThreadPriority());
        Thread secondThread = new Thread(new ThreadPriority());

        firstThread.setPriority(Thread.NORM_PRIORITY);
        secondThread.setPriority(Thread.MAX_PRIORITY);

        firstThread.start();
        secondThread.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

The thread priority of the running thread is 5
The thread priority of the running thread is 10

Process finished with exit code 0
Enter fullscreen mode Exit fullscreen mode

It is important to note that thread priorities alone do not guarantee the order in which threads execute.

An Operating System’s Thread Scheduler determines the order with which threads run. A simple implementation of the Thread Scheduler ensures that threads with the same level of priority are assigned processor resources in a round-robin fashion. However, Thread Schedulers are not that simple. Concurrency is a complex concept and the JVM provides utilities that abstract these complexities from the programmer.

Starvation and DeadLock

Thread priorities determine the order in which threads are executed. Depending on the Operating System, a steady influx of higher priority threads could cause lower priority threads to stay in the waiting state indefinitely. Such indefinite postponement of thread execution is called starvation.
Closely, related to starvation is the concept of DeadLock. To explain Deadlock, let us say that there are two threads- Thread 1 and Thread 2. Thread 1 cannot execute because it is waiting (directly or indirectly) for Thread 2 to execute. Simultaneously, Thread 2 cannot execute because it is waiting for Thread 1 to execute. Both Thread 1 and Thread 2 are blocking each other, hence the term deadlock. The concept of deadlock concurrency is analogous to a traffic jam.

Conclusion

In this article, you learnt about the different states of Java threads, from time of creation to termination.
I hope that you enjoyed the article and learnt something new too. You can follow me on Twitter on @ehizmanhttps://twitter.com/ehizman_tutorEd. This will help me write more valuable content.

Special shout-outs to Confidence Okere, Oladimeji Omotosho, and Ozi Okoroafor for helping me with reviews and edits.

I look forward to hearing from you. Always code with ❤️

Oldest comments (0)