Multithreading in Java

Reading Time: 5 minutes

Hello, Everyone in this blog I’m going to explain the Important aspects of multithreading in Java. In this blog, we are going to see what is thread, what is multithreading, and why we need this. We are also going to learn How we can Implement multithreading in Java.

multithreading-image

What is Thread?

In Java, a thread is like a separate path of execution within a program that can run independently of the main program. It allows multiple things to happen at the same time within a program. For example, a program can use one thread to perform a time-consuming task while using another thread to update the user interface. This can improve the performance and responsiveness of the program. So in simple language Thread is a lightweight process that executes some tasks.

What is a Single Thread?

In Java, a single thread refers to a program that has only one path of execution. This means that the program can only perform one task at a time. The program starts at the beginning of its code and executes each line of code in sequence until it reaches the end. Once the program has finished executing, it terminates.

In a single-threaded program, any long-running tasks can cause the program to become unresponsive, as the program will not be able to respond to user input or perform other tasks until the long-running task has been completed.

Below is an example of a single-threaded program. If you will see the output of this program Here we have provided Thread.sleep(200) in for loop which means it has to wait for 0.2 sec after each iteration, but still worker2 starts executing the task when worker1 completed his all task.

public class SupervisorExample{

    public static void main(String[] args){

      Worker1 worker1 = new Worker1();

      Worker2 worker2 = new Worker2();

        try{

            worker1.executeWork();

        }catch(InterruptedException e){

            e.printStackTrace();

        }

        worker2.executeWork();
    }
}
class Worker1{

  public void executeWork() throws InterruptedException{

      for(int i=1; i<6; i++){

          Thread.sleep(200);

          System.out.println("worker 1 is executing the task "+ i);
      
     }

  }
}
class Worker2{

    public void executeWork(){

        for(int i=1; i<6; i++){

            try{

                Thread.sleep(200);

            }catch(InterruptedException e){

                e.printStackTrace();

            }

            System.out.println("worker 2 is executing the task "+ i);

        }
    }
}

//output

worker 1 is executing the task 1

worker 1 is executing the task 2

worker 1 is executing the task 3

worker 1 is executing the task 4

worker 1 is executing the task 5

worker 2 is executing the task 1

worker 2 is executing the task 2

worker 2 is executing the task 3

worker 2 is executing the task 4

worker 2 is executing the task 5



What is Multithreading?

Multithreading in Java refers to the ability to create and manage multiple threads within a single program. A program can create multiple threads, each of which can execute its own sequence of instructions concurrently with other threads. Multithreading allows a program to perform several tasks simultaneously, which can improve the program’s performance and user experience.

There are 2 ways to implement multithreading

  • By Implementing Runnable Interface
  • By Extending the Thread class

By Extending the Thread class

In this approach, we create a new class that extends the Thread class and overrides the run() method to define the code that will run in the new thread. Then we create an instance of this class and call the start() method to begin executing the run() method in a separate thread. Here is the example:

public class SupervisorExampleWithThread{

    public static void main(String[] args){

       ParallelWorker1 parallelWorker1 = new ParallelWorker1();

       ParallelWorker2 parallelWorker2 = new ParallelWorker2();

       parallelWorker2.start();

       parallelWorker1.start();

    }

}

class ParallelWorker1 extends Thread{

    @Override

    public void run(){

        for(int i=1; i<6; i++){

            try{

                Thread.sleep(200);

            }catch(InterruptedException e){

                e.printStackTrace();

            }

            System.out.println("worker 1 is executing the task "+i);

        }
    }
}

class ParallelWorker2 extends Thread{

    @Override

    public void run(){

        for(int i=1; i<6; i++){

            try{

                Thread.sleep(200);

            }catch(InterruptedException e){

                e.printStackTrace();
            }

            System.out.println("worker 2 is executing the task "+i);

        }
    }
}

//output

worker 2 is executing the task 1

worker 1 is executing the task 1

worker 2 is executing the task 2

worker 1 is executing the task 2

worker 2 is executing the task 3

worker 1 is executing the task 3

worker 2 is executing the task 4

worker 1 is executing the task 4

worker 2 is executing the task 5

worker 1 is executing the task 5

By Implementing Runnable Interface

In this approach, we create a new class that implements the Runnable interface and overrides the run() method to define the code that will run in the new thread. Then we create an instance of the class and pass it as a parameter to a new Thread object. Finally, we call the start() method on the Thread object to begin executing the run() method in a separate thread. Here is the example.

public class SupervisorExampleUsingRunnableInterface{

    public static void main(String[] args){

        PWorker1 pWorker1= new PWorker1();

        PWorker2 pWorker2 = new PWorker2();

        Thread thread1 = new Thread(pWorker1);

        Thread thread2 = new Thread(pWorker2);

        thread1.start();

        thread2.start();

    }

}

class PWorker1 implements Runnable{

    @Override

    public void run(){

        for(int i=1; i<6; i++){

            try{

                Thread.sleep(200);

            }catch(InterruptedException e){

                e.printStackTrace();

            }

            System.out.println("worker 1 is executing the task "+i);

        }
    }
}

class PWorker2 implements Runnable{

    @Override

    public void run(){

        for(int i=1; i<6; i++){

            try{

                Thread.sleep(200);

            }catch(InterruptedException e){

                e.printStackTrace();

            }

            System.out.println("worker 2 is executing the task "+i);
        }
    }
}

//output

worker 2 is executing the task 1

worker 1 is executing the task 1

worker 2 is executing the task 2

worker 1 is executing the task 2

worker 2 is executing the task 3

worker 1 is executing the task 3

worker 2 is executing the task 4

worker 1 is executing the task 4

worker 2 is executing the task 5

worker 1 is executing the task 5

In the above two examples of multithreading If you will see the output both workers are working parallel which makes execution faster. Now it is obvious why we need multithreading, Let’s see some other benefits of multithreading in the below section.

Why We Need Multithreading in Java

Multithreading is a powerful feature of modern programming languages like Java that allows a program to perform multiple tasks concurrently. In this blog, we will discuss the various reasons why we need multithreading in Java.

To improve program performance

One of the most important reasons for using multithreading in Java is to improve program performance. By running multiple tasks concurrently, a program can take advantage of modern CPUs that typically have multiple cores. This allows the program to execute more tasks in parallel, which can significantly improve program performance.

To enhance user experience

Multithreading can be used to enhance the user experience of a program. For example, in a graphical user interface (GUI) program, you may want to keep the user interface responsive while performing a long-running task in the background. By running the long-running task in a separate thread, the user interface can remain responsive, providing a better user experience.

To handle I/O operations

Multithreading is particularly useful when dealing with input/output (I/O) operations, such as reading from or writing to a file, network communication, or database access. These operations can often be slow and block the execution of the program, making it unresponsive. By performing I/O operations in a separate thread, the program can continue to execute other tasks while waiting for the I/O operation to complete.

To take advantage of multiple CPUs

Multithreading is especially useful in programs that need to perform computationally intensive tasks. For example, a program that performs image processing, video encoding, or data analysis can benefit greatly from multithreading. By dividing the workload among multiple threads, the program can take advantage of multiple CPUs and execute the tasks faster.

To improve scalability

Multithreading can improve the scalability of a program. As the workload increases, a program can create additional threads to handle the additional load, allowing it to scale up to meet the demand. This can be particularly useful in server applications that need to handle a large number of client requests.

Conclusion

In conclusion, multithreading is a powerful feature of Java that allows a program to perform multiple tasks concurrently, improving program performance, enhancing user experience, handling I/O operations, taking advantage of multiple CPUs, and improving scalability. By using multithreading in your programs, you can create more efficient, responsive, and scalable applications.

If you want to explore more about multithreading click here.

Written by 

I'm a Software Consultant at Knoldus . I have completed my B.tech in Computer Science stream from IMS Engineering College, Ghaziabad. I love to explore new technologies and have great interest in problem solving.