Multithreading in Java | Part-1

In this tutorial series, we are learning multithreading in Java. This is the first article in the series. Here we are going through the basic concept of multithreading.


Multitasking

Executing several tasks simultaneously is the concept of multitasking
There are two types of multitasking:

  1. Process-based Multitasking

  2. Thread based Multitasking

Process-based Multitasking

Executing several tasks simultaneously where each task is a separate independent program(process) is called process-based multitasking.
For e.g. While typing java code, you can listen to songs, at the same time you can download your favorite movie. So all these tasks are executing simultaneously and independently of each other.
It is best suitable at the Operating System level.

Thread based Multitasking

Executing several tasks simultaneously where each task is a separate path of the same program. Each independent path is nothing but the thread.
It is best suitable at the Programming level.

The objective of multithreading is to reduce the idle time of the processor and improve the performance of the system

Application of Multithreading:

  1. To develop multimedia graphics

  2. To develop Animation

  3. To develop Video games

  4. To develop Application server and Webserver

Developing multithreading application in java is very easy because Java provides inbuilt support for multithreading with rich API

The way of defining a thread


We can define a thread in the following two ways:

  1. By extending Thread class

  2. By implementing Runnable Interface

1) By extending Thread class

class MyThread extends Thread{
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println("Child thread : "+i);
        }

    }
}
public class Test1 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("Parent thread : "+i);
        }
    }
}
o/p:-
Main Thread - 0
Main Thread - 1
Child Thread - 0
Main Thread - 2
Child Thread - 1
Main Thread - 3
Child Thread - 2
Main Thread - 4
Child Thread - 3
Child Thread - 4

2) By implementing Runnable Interface

Runnable Interface present in java.lang package and contains only one method i.e. public void run()

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Child thread : " + i);
        }

    }
}

public class Test1 {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread mt = new Thread(mr);
        mt.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("Parent thread : " + i);
        }
    }
}
Parent thread : 0
Parent thread : 1
Parent thread : 2
Child thread : 0
Child thread : 1
Child thread : 2
Child thread : 3
Parent thread : 3
Child thread : 4
Parent thread : 4

Thread Scheduler

It is part of JVM responsible to schedule threads i.e if multiple threads are waiting for execution then which thread execute next is decided by the thread scheduler. We can't expect an exact algorithm followed by thread schedular. It is varied from JVM to JVM. Hence we can't predict order and exact output.

Difference between t.start() and t.run()

  • When t.start() called new thread will be created, which is responsible for the execution of the run() method.

  • When t.run() called new thread won't be created, and run() method will be executed just like a normal method called by the main thread.
    In above program if we replace mt.start() with mt.run() then o/p will be:

    Child thread : 0
    Child thread : 1
    Child thread : 2
    Child thread : 3
    Child thread : 4
    Parent thread : 0
    Parent thread : 1
    Parent thread : 2
    Parent thread : 3
    Parent thread : 4
    

Importance of thread class start() method

The Thread class start() method is responsible for registering the thread with the thread schedular and all other registration activities. Hence without executing the start() method there is no chance of starting a new thread in java.

t.start(){
	1) Registring this thread in thread schedular.
	2) Perform all mandatory activities.
	3) Call run() method.
}

Overload run() method

We can overload run() method, but Thread classes start() method can invoke only no-args run() method. The other overloaded methods we have to call explicitly like normal methods.

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Child thread : " + i);
        }
    }
    public void run(int i){
        for (int j = 0; j < i; j++) {
            System.out.println("run method call : "+ j);
        }
    }
}

public class Test1 {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread mt = new Thread(mr);
        mt.start();
        mr.run(2);
        for (int i = 0; i < 5; i++) {
            System.out.println("Parent thread : " + i);
        }
    }
}
O/P:
run method call : 0
Child thread : 0
run method call : 1
Child thread : 1
Parent thread : 0
Child thread : 2
Parent thread : 1
Child thread : 3
Child thread : 4
Parent thread : 2
Parent thread : 3
Parent thread : 4

If we are not overriding run() method the Thread class run() method will be executed which has empty implementation and we can't get any output. Hence it is highly recommended to override run() method otherwise don't go for the multithreading concept.

class MyThread extends Thread {
}

public class Test1 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
    }
}

O/P - // No output

Overriding start() method

If we override the start method then our start() method will be executed just like a normal method call and a new thread won't be created.

class MyThread extends Thread {
    public void start(){
        System.out.println("Override start()");
    }
    public void run(){
        System.out.println("Child method.");
    }
}

public class Test1 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
        System.out.println("Main method");
    }
}
Override start()
Main method

It is not recommended to override start() method, otherwise don't go for multithreading.

Note:- After starting a thread if we are trying to restart the same thread then we will get runtime exception Illegalthreadstateexception.

Example
class MyThread extends Thread {
    public void run(){
        System.out.println("Child method.");
    }
}

public class Test1 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
        mt.start();
    }
}

Let's see what will happen in the following cases:

MyRunnable r = new MyRunnable();
Thread t1 = new Thread();
Thread t2 = new Thread(r)

t1.start()

  • A new thread will be created which is responsible for the execution of the Thread class run() method, which has an empty implementation.

t1.run()

  • No new thread will be created. Thread class run() method will be executed just like a normal method

t2.start()

  • A new thread will be created which is responsible for the execution of MyRnnable run() method will be executed just like normal method

t2.run()

  • New thread will not be created and MyRunnable run() method will be executed just like normal method

r.start()

  • Get compile-time error saying that MyRunnable class doesn't have the capability to start a thread

r.run()

  • No new thread will be created and MyRunnable run() method will be executed just like a normal method call

Which approach is best to define thread?

Among 2 ways of defining thread, implements a runnable approach is recommended to use. Because in the 1st approach our class extends thread class, there is no chance of extending any other class hence we are missing inheritance benefits here. But in the second approach while implementing the Runnable interface we can extend any other class.

Summary of thread constructors

  Thread t = new Thread()
Thread t = new Thread(Runnable r)
Thread t = new Thread(String name)
Thread t = new Thread(Runnable r, String name)
Thread t = new Thread(ThreadGroup g, String name)
Thread t = new Thread(ThreadGroup g, Runnable r)
Thread t = new Thread(ThreadGroup g, Runnable r, String name)

Getting and Setting name to the thread

Every thread in java has some name, it may be the default name generated by JVM or a customized name provided by the programmer.
Methods :

  • getName() - To get thread name

  • setName() - To set thread name

Thread Priorities

- Priorities is always in range of 1 to 10
- Min Priority = 1
- Max Priority = 10
Every thread in Java has some priority. It may be default priority generated by JVM or priority is given by the programmer.

Default Priorities by Thread class :

  • Thread.MIN_PRIORITY = 1

  • Thread.MAX_PRIORITY = 10

  • Thread.NORM_PRIORITY = 5

Thread schedular will use priorities while allocating processor. Thread having the highest priority will get the chance first. If 2 threads having the same priority then we can't expect the exact execution order. It depends on the thread schedular.
Example of getting and setting the priority of thread:

package mt;

public class ThreadPriorityExample {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + " : " + i);
                }
            }
        });
        
        System.out.println("Main class thread name : " + Thread.currentThread().getName());
        System.out.println("Newly created thread name : " + t.getName());

        t.setName("Local worker thread");
        System.out.println("Name assigned to new thread using - t.setName() : " + t.getName());

        t.setPriority(2);
        Thread.currentThread().setPriority(10);
        System.out.println("Main class thread priority : " + Thread.currentThread().getPriority());
        System.out.println("Assigned highest priority to new thread using - t.setPriority() : " + t.getPriority());
        
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

/*

Output -
Main class thread name : main
Newly created thread name : Thread-0
Name assigned to new thread using - t.setName() : Local worker thread
Main class thread priority : 10
Assigned highest priority to new thread using - t.setPriority() : 2
main : 0
main : 1
main : 2
main : 3
main : 4
Local worker thread : 0
Local worker thread : 1
Local worker thread : 2
Local worker thread : 3
Local worker thread : 4
 */

Note: The default priority of the main thread is 5. For all remaining thread except the main thread, the default priority is inherited from its parent thread.

Summary of this post:

What we learned?

  • What is multitasking?

  • Difference between process-based and thread-based multitasking

  • Way of defining threads

  • What is thread schedular

  • Differenc between t.start() and t.run()

  • Importance of start() method

  • Impact of overloading run() method

  • Overriding start() method

  • Overriding start() method

With this, we also came to the end of this article. Please let us know your views and opinions in the comment section below. I hope you enjoyed this post. Please stay with us for more posts.
For more details on multithreading please refer Video

0 comments:

Post a Comment