Page Content

Tutorials

What is Multithreading in Java? & What is Thread Life Cycle

Multithreading in Java

A key component of Java is multithreading, which enables a software to carry out multiple tasks at once. Developing interactive, networked programming and building seamless applications require this capacity. Especially in systems with multiple CPUs or cores, multithreading maximises the utilisation of available computer resources by allowing various tasks to run simultaneously within the same software.

The capacity of multithreading to efficiently use idle time is one of its main benefits. To avoid the CPU sitting idle, for example, another thread can carry out a different activity while the other portion of your application waits for a slow I/O action (such as network data or user input). As a result, apps become more responsive and efficient. Because Java supports multithreading by default, many of the fundamental challenges of concurrent execution management are abstracted away.

Differences between multiple processes and multiple threads

Making the distinction between thread-based and process-based multitasking is crucial to comprehending multithreading.

  • Process-based multitasking: This is the capacity of an operating system to run two or more different programs at the same time. As a self-contained execution environment with a distinct address space, each program, or “process,” is comparatively heavy-weight. Process-based multitasking includes, for instance, using a word processor and a web browser simultaneously. Process-based multitasking is used by Java programs, but the operating system manages this feature; Java has no control over it.
  • Thread-based multitasking: A single software can execute two or more tasks at once thanks to thread-based multitasking. Within a process, a “thread” is a smaller, lighter unit of execution that and other threads share the same address space. Compared to inter-process communication, inter-thread communication is less costly, and context switching, or moving between threads, has less overhead. Text editors, for instance, may format text in one thread while printing in another. The purpose of Java’s multithreading features is to facilitate thread-based multitasking.

Java Thread Life Cycle

A thread goes through several phases, or states, while it is in existence. There are four main thread states defined by Java:

Java Thread Life Cycle
Java Thread Life Cycle
  1. New: A Thread object is in the new state when it is initially created using the new operator until its start() method is called. The thread is “born” at this point, but it isn’t running any code.
  2. Runnable: The thread enters the runnable state when the start() function is called. The thread is in this condition, ready to run, and is awaiting the thread scheduler to assign it a CPU. A thread may be in a “queued” sub-state while it is waiting for its turn, or it may be in a “running” sub-state while it is actively executing code. With the help of variables like thread priorities, the thread scheduler decides which runnable threads execute first.
  3. Blocked / Not Runnable: The term “blocked” or “not runnable” refers to a number of situations in which a runnable thread may momentarily exit the running state and become “not runnable.”
    • Waiting for a resource: When a thread requires a resource that is presently being held by another thread (for example, while awaiting the acquisition of a lock, I/O completion, or the use of the wait() method).
    • Sleeping: The Thread.sleep() method causes the thread to pause for a certain amount of time.
    • Suspended: Explicit thread suspension was possible in earlier Java versions, but it is now deprecated. In contemporary practice, wait()/notify() and flag variables are used for graceful control.
  4. Terminated / Dead: A thread exits in any other way or when its run() method finishes its job, putting it in the terminated state. It is not possible to resume or restart a thread once it has ended.

Creating Threads: Extending the Thread class

Java.lang.Thread is a class that can be subclassed to create a thread. Creating a new class that extends Thread and then overriding the run() method with the code that the new thread will execute are the steps involved in this method.

Here’s an example:

// Create a second thread by extending Thread
class NewThread extends Thread {
    NewThread() {
        // Create a new, second thread and name it
        super("Demo Thread"); 
        System.out.println("Child thread: " + this);
        start(); // Start the thread 
    }
    // This is the entry point for the second thread.
    public void run() { 
        try {
            for(int i = 5; i > 0; i--) {
                System.out.println("Child Thread: " + i);
                Thread.sleep(500); 
            }
        } catch (InterruptedException e) {
            System.out.println("Child interrupted.");
        }
        System.out.println("Exiting child thread.");
    }
}
class ExtendThread {
    public static void main(String args[]) {
        new NewThread(); // create a new thread 
        try {
            for(int i = 5; i > 0; i--) {
                System.out.println("Main Thread: " + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println("Main thread interrupted.");
        }
        System.out.println("Main thread exiting.");
    }
}

Code Output:

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

Here, Thread is extended by the NewThread class. The thread is named by its constructor using super("Demo Thread"), and its execution is initiated by calling start(). The child thread’s code is contained in the run() procedure. ExtendThread main() method automatically launches the child thread by creating an instance of NewThread. The child thread and the main thread both operate at the same time.

Creating Threads: Implementing the Runnable interface

Creating a thread by implementing the java.lang.Runnable interface is the second and frequently recommended method. An implementation of the public void run() method, which holds the code that the thread is to execute, must be provided by a class that implements Runnable. A Thread object is instantiated by supplying the Runnable instance to its constructor after an object of this Runnable type has been created. In order to start execution, you lastly invoke the start() function on this Thread object.

Here’s an example:

// Create a thread by implementing Runnable.
class MyThread implements Runnable {
    Thread thrd;
    String thrdName;
    // Construct a new thread.
    MyThread(String name) {
        thrdName = name;
        thrd = new Thread(this, name); 
        System.out.println("New thread: " + thrd);
        thrd.start(); // Start the thread 
    }
    // Entry point of thread.
    public void run() { 
        System.out.println(thrdName + " starting.");
        try {
            for(int count=0; count < 5; count++) {
                Thread.sleep(400); 
                System.out.println("In " + thrdName +
                                   ", count is " + count);
            }
        }
        catch(InterruptedException exc) {
            System.out.println(thrdName + " interrupted.");
        }
        System.out.println(thrdName + " terminating.");
    }
}
class UseThreadsImproved {
    public static void main(String args[]) {
        System.out.println("Main thread starting.");
        MyThread mt1 = new MyThread("Child #1"); 
        MyThread mt2 = new MyThread("Child #2"); 
        for(int i=0; i<15; i++) { // Adjusted loop for concise output
            System.out.print(".");
            try {
                Thread.sleep(100);
            }
            catch(InterruptedException exc) {
                System.out.println("Main thread interrupted.");
            }
        }
        System.out.println("Main thread ending.");
    }
}

Code Output:

Main thread starting.
New thread: Thread[Child #1,5,main]
New thread: Thread[Child #2,5,main]
.Child #1 starting.
.Child #2 starting.
.In Child #1, count is 0
.In Child #2, count is 0
.In Child #1, count is 1
.In Child #2, count is 1
.In Child #1, count is 2
.In Child #2, count is 2
.In Child #1, count is 3
.In Child #2, count is 3
.In Child #1, count is 4
.Child #1 terminating.
.In Child #2, count is 4
.Child #2 terminating.
Main thread ending.

Runnable is implemented by MyThread in this example. Its constructor calls thrd.start() after creating a new Thread object (thrd) and handing it (the MyThread instance) along with a name. The task for the thread is defined by the run() method. Several MyThread instances are created by the main() method in UseThreadsImproved, and each one automatically launches a new child thread.

Choosing an Approach

Java threads can be created by either implementing Runnable or extending Thread, and both methods eventually employ the Thread class for control and creation. But while selecting one over the other, there are factors to take into account:

  • In general, using Runnable is advised for increased flexibility. Your class cannot extend any other class if it extends Thread since Java does not permit multiple inheritance. Your thread-enabled class can extend another class if your design calls for it by implementing Runnable.
  • It may appear a little simpler to implement extending Thread for straightforward, self-contained operations when no additional inheritance is required.
  • Runnable is also a better option for high-level thread management APIs that take Runnable or Callable objects, including ExecutorService and thread pools.

The decision ultimately comes down to your particular design specifications. Nonetheless, developers are frequently influenced to use Runnable by the “favour composition over inheritance” philosophy.

Index