Java Threads – Dynamic Life Cycle

Before everything, let’s understand what is a threads in Java and what is multithreading.

In Java, a `thread` is a unit of execution that enables `concurrent programming`. It allows multiple tasks to be executed simultaneously within a program. `Threads` operate within a `single process`, `share memory`, and enable efficient `resource utilization`. They are vital for developing responsive and efficient applications.

`Multithreading` in Java enables running multiple tasks simultaneously within a program. It improves `efficiency` and `responsiveness` by using threads in Java. Threads execute tasks concurrently, making programs faster and more responsive. Java provides built-in features for multithreading, making it easier to implement. Multithreading enhances performance and user experience in applications.

The java.lang.Thread package is used to implement multithreading in Java. Thread class, which includes a static enumeration called State that defines a thread’s potential states. These states represent the stages of execution that a thread goes through. A thread can be in one of the following states at any given time:

  • New
  • Active
  • Blocked
  • Waiting
  • Timed Waiting
  • Terminated

Thread States In Its Life Cycle

In the life cycle of a thread in Java, a thread goes through different states that define its behavior and progression. Understanding these states is crucial for effective thread management. Let’s explore the various states a thread can be in:

  • New: A thread is in the “new” state when it is created but has not yet started its execution.
  • Active: Within Java threads, when in the “active” state, a thread can co-exist in two states i.e. “runnable” or “running”. It may be waiting for the processor to allocate CPU time. Threads in this state can be executing, or they may be waiting for their turn to execute.
  • Blocked: A thread enters the “blocked” state when it is temporarily inactive. It occurs when a thread is waiting for a resource that is currently unavailable, such as a lock or a synchronized block held by another thread.
  • Waiting: Java Threads in the “waiting” state are waiting indefinitely until another thread interrupts them or a specific condition is met. They are not actively executing or performing any task.
  • Timed Waiting: Similar to the “waiting” state, threads in the “timed waiting” state are waiting for a specific period of time. They will resume execution after the specified time elapses or if they are interrupted.
  • Terminated: A thread enters the “terminated” state when it completes its execution or is explicitly terminated. Once a thread is terminated, it cannot be resumed or restarted.
Thread Life Cycle In Java

Thread States In Detail 

New State  

In Java, when a thread is created but has not yet started its execution, it is in the “New” state. In this state, the necessary resources are allocated for the thread by the Java Virtual Machine (JVM), such as memory and a unique thread ID. However, the thread has not begun executing its target task or running any code.

Implementation of the “New” State  

The implementation of the “New” state is performed through the instantiation of the Thread class or any of its subclasses. When a new thread object is created using the new keyword and the Thread class constructor, the thread is initialized but not yet started.

For example, 

public class ThreadState {
    public void createNewThread() {

        // Create a new thread
        Thread thread = new Thread(() -> {});
        // Print the thread's state (should be "NEW" at this point)
        System.out.println("Thread state: " + thread.getState());
    }
}

// Create a new instance of ThreadState
ThreadState threadState = new ThreadState();
// Call the createNewThread() method to create a new thread and print its state
threadState.createNewThread();

In the above example, we created a new thread by invoking the Thread constructor with a lambda expression. This lambda expression creates the functional interface Runnable with an empty `run()` method body.

Output:

Thread state: NEW

Active State  

In Java, the “Active” state of a thread refers to the phase when the thread is executing its target task or code. This state indicates that the thread is actively running and utilizing system resources to perform its designated operations.

Implementation of the “Active” State – 

The “Active” state can exist in two sub-states: “Runnable” and “Running.”

Runnable State: When a thread is in the “Runnable” state, it is eligible to be executed by the CPU but is waiting for its turn. The thread is in a runnable queue and awaits scheduling by the operating system. The exact order of execution depends on the thread scheduler. In the “Runnable” state, the thread is ready to execute and can transition to the “Running” state when the CPU assigns it processing time.

Running State: When a thread is in the “Running” state, it is actively executing its code. The CPU is currently executing the thread’s instructions, and it is utilizing system resources to perform its tasks. The thread remains in the “Running” state until it completes its execution or yields the CPU voluntarily.

In Java, the “active” state of a thread consists of the “runnable” and “running” states. The JVM and the operating system handle the transition from the “runnable” state to the “running” state.

For example, 

public class MyThread extends Thread {
    public void run() {

        // Code to be executed by the thread
        System.out.println("Thread is running!");
    }

    public static void main(String[] args) {
        // Create a new instance of MyThread
        MyThread thread = new MyThread();

        // Start the thread
        thread.start();

        // Perform some other tasks in the main thread
        System.out.println("Main thread is performing other tasks.");
    }
}

In this code example, we create a class `MyThread` that extends the `Thread` class. The `run()` method is overridden to define the code to be executed by the thread. In the `main()` method, we create an instance of `MyThread`, start the thread using `start()`, and perform other tasks in the main thread.

The output will be non-deterministic due to concurrent execution. However, you can expect something like:

Output:

Main thread is performing other tasks.
Thread is running!

This demonstrates the active state of the thread, where it is both “runnable” and “running” at different stages.

Blocked State 

In the life cycle of a thread, the “blocked” state represents a situation where a thread is temporarily inactive. It occurs when a thread is waiting for a certain condition to be satisfied, such as acquiring a lock on an object or waiting for input/output operations to complete.

When a thread is blocked, it is not eligible for execution until the condition it is waiting for is fulfilled. Once the condition is satisfied, the thread transitions to the “runnable” state and can be scheduled for execution by the JVM.

For example, 

public class BlockedThreadExample {
    private static Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1 has acquired the lock.");
                try {
                    Thread.sleep(2000); // Simulating a delay
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2 has acquired the lock.");
            }
        });

        thread1.start();
        try {
            Thread.sleep(1000); // Giving thread1 a head start
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

In this Java example, we have two threads, `thread1` and `thread2`, sharing a common lock object. `thread1` acquires the lock using a `synchronized block` and then goes into a `sleep` for 2 seconds to simulate a delay. Meanwhile, `thread2` tries to acquire the same lock but is blocked until `thread1` releases it.

Output:

Thread 1 has acquired the lock.

As you can see, `thread2` is blocked and unable to acquire the lock until `thread1` releases it. This demonstrates the blocked state of a thread, where it is waiting for a certain condition to be fulfilled.

Waiting State

In the life cycle of a thread, the “waiting” state represents a situation where a thread voluntarily gives up the CPU and waits for another thread to `notify` it. It occurs when a thread calls the `Object.wait()` method.

When a thread enters the waiting state, it releases any locks it holds and remains inactive until another thread invokes the `notify()` or `notifyAll()` method on the same object. Once the thread receives a notification, it transitions back to the “runnable” state and can be scheduled for execution by the JVM. The waiting state is typically used for inter-thread communication and synchronization, where one thread waits for a specific condition to be met before proceeding further.

For example, 

public class WaitingThreadExample {
    public static void main(String[] args) {
        final Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 1 is entering the waiting state.");
                    lock.wait(); // Thread 1 waits until notified
                    System.out.println("Thread 1 has received a notification and resumed execution.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 2 is performing some processing.");
                    Thread.sleep(2000);
                    System.out.println("Thread 2 has finished processing and notifying Thread 1.");
                    lock.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread has finished execution.");
    }
}

In this Java example, we have two threads, `thread1` and `thread2`. `thread1` enters the waiting state by calling `lock.wait()`, while `thread2` performs some processing and then notifies `thread1` using `lock.notify()`.

Output:

Thread 1 is entering the waiting state.
Thread 2 is performing some processing.
Thread 2 has finished processing and notifying Thread 1.
Thread 1 has received a notification and resumed execution.
Main thread has finished execution.

As you can see, `thread 1` enters the waiting state and waits until `thread 2` notifies it. Once `thread 2` invokes `lock.notify()`, `thread 1` receives the notification and resumes execution.

Timed-Waiting State

In the life cycle of a thread, the “timed waiting” state represents a situation where a thread is temporarily inactive for a specific period of time. It occurs when a thread calls methods that involve waiting with a timeout, such as Thread.sleep() or Object.wait(timeout).

When a thread enters the timed waiting state, it will remain inactive for the specified duration or until it receives a notification from another thread. Once the specified time has elapsed or the thread is notified, it transitions back to the “runnable” state and can be scheduled for execution by the JVM.

For example, 

public class TimedWaitingThreadExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("Thread is entering the timed waiting state.");
                Thread.sleep(3000); // Thread will sleep for 3 seconds
                System.out.println("Thread has woken up from the timed waiting state.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread.start();
        System.out.println("Main thread is continuing its execution.");

        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread has finished execution.");
    }
}

In this example, we have a thread that calls the `Thread.sleep(3000)` method, causing it to enter the timed waiting state for 3 seconds. Meanwhile, the main thread continues its execution and waits for the child thread to complete using `thread.join()`.

Output:

Main thread is continuing its execution.
Thread is entering the timed waiting state.
Thread has woken up from the timed waiting state.
Main thread has finished execution.

As you can see, the thread enters the timed waiting state for 3 seconds, during which it is inactive. After the specified time has elapsed, it wakes up and continues its execution. This demonstrates the timed waiting state of a thread.

Terminated State

In the life cycle of a thread, the “terminated” state represents the final state of a thread where its execution has been completed. Once a thread finishes executing its run method or encounters an exception that is not caught, it transitions to the terminated state. A terminated thread cannot be scheduled for execution again.

When a thread reaches the terminated state, it has finished its task and is no longer active. At this point, the thread’s resources are released by the JVM, and it can no longer participate in the execution of the program.

The terminated state is the natural end of a thread’s life cycle, and it occurs when the run method has finished its execution or an unhandled exception occurs.

For example,

public class TerminatedThreadExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread is executing some task.");
            // Simulating some work
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread has finished its task.");
        });

        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread has finished execution.");
    }
}

In this example, we create a new thread `thread` that performs some task, simulating work with a delay of 2 seconds. Once the task is completed, the thread reaches the terminated state.

Output:

Thread is executing some task.
Thread has finished its task.
Main thread has finished execution.

As you can see, the `thread` starts executing its task, and after completing it, it reaches the terminated state. The main thread waits for thread to finish using `thread.join()`, and then continues its execution.

Llige Cycle of a Threads

Java Program To Demonstrate Thread States

public class ThreadLifeCycleExample {
    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            System.out.println("Thread is in the New state.");
            System.out.println("Thread is transitioning to the Runnable state.");
            System.out.println("Thread is now in the Runnable state.");
           
            // Performing some task
            System.out.println("Thread is executing some task.");
           
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
            System.out.println("Thread is transitioning to the Blocked state.");
            synchronized (ThreadLifeCycleExample.class) {
                System.out.println("Thread is now in the Blocked state.");
               
                // Waiting for another thread to release the lock
                try {
                    Thread.sleep(2000);
                    ThreadLifeCycleExample.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
            System.out.println("Thread is transitioning to the Timed Waiting state.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread is now in the Timed Waiting state.");
           
            System.out.println("Thread is transitioning to the Waiting state.");
            synchronized (ThreadLifeCycleExample.class) {
                try {
                    ThreadLifeCycleExample.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
            System.out.println("Thread is transitioning to the Terminated state.");
        });

        System.out.println("Thread is in the New state.");
        thread.start();
       
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       
        System.out.println("Thread is transitioning to the Running state.");
       
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       
        System.out.println("Thread is transitioning to the Terminated state.");
       
        synchronized (ThreadLifeCycleExample.class) {
            ThreadLifeCycleExample.class.notify();
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread has finished execution.");
    }
}

Output:

Thread is in the New state.
Thread is transitioning to the Running state.
Thread is now in the Runnable state.
Thread is executing some task.
Thread is transitioning to the Blocked state.
Thread is now in the Blocked state.
Thread is transitioning to the Timed Waiting state.
Thread is now in the Timed Waiting state.
Thread is transitioning to the Waiting state.
Thread is transitioning to the Terminated state.
Thread is transitioning to the Terminated state.
Main thread has finished execution.

In this code example, we create a new thread and demonstrate each state of the thread’s life cycle, including `New`, `Active`, `Blocked`, `Timed Waiting`, `Waiting`, and `Terminated`. The output showcases the transition between different states and the overall flow of the thread’s life cycle.

Conclusion

  • Understanding the life cycle of a thread in Java is crucial for effective multithreaded programming.
  • Thread states, including New, Runnable, Blocked, Timed Waiting, Waiting, and Terminated, play a significant role in managing thread behaviour.
  • Synchronisation mechanisms, such as synchronised blocks and methods, help ensure thread safety and prevent race conditions.
  • The ‘Timed Waiting’ and ‘Waiting’ states in Java threads enable pausing execution until conditions are met or notification from other threads occurs.
  • Knowledge of thread states helps diagnose issues like deadlock and optimize thread utilization in concurrent applications.

Happy Threading and Stating!

Leave a Reply

Your email address will not be published. Required fields are marked *