Multithreading.

Multitasking:

Executing several tasks simultaneously is a 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.
Example: while typing a java program in the editor, we can listen audio songs from the same system at the same time we can download a file from the net. All these tasks will be executed simultaneously and independent of each other.

  • Hence it is process based multitasking.
  • Process based multitasking is best suitable at OS level.

Thread Based Multitasking:

Executing several tasks simultaneously where each task is separate independent part of the same program is called Thread based Multitasking and each independent part is called a Thread.
Thread based Multitasking is best suitable at Programmatic level.
Whether it is Process based or Thread based the main objective of multitasking is to reduce response time of the system and to improve performance.
The main important application areas of multithreading are

  • To develop multimedia graphics.
  • To develop animations.
  • To develop video games.
  • To develop web servers and applications servers etc.

Thread:

Thread is a flow/Process of execution or light weight process.

Defining a Thread:

We can define a thread in the following two ways

  1. By extending Thread class
  2. By implementing Runnable interface

By Extending Thread class:

//Defining a Thread
class MyThread extends Thread{
    @Override
    public void run(){
        //Job of a Thread
        //Executed by child class
        for(int i = 0; i < 5; i++) {
            System.out.println("Child Thread");
        }
    }
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();      
        //Thread Instantiation
        t.start();
        // Starting of a Thread
        //Executed by Main Thread
        for(int i = 0; i < 5; i++){
            System.out.println("Main Thread");
        }
    }
}

Case 1: Thread Schedular:

It is the part of JVM and it is responsible to schedule Threads i.e., if multiple threads are waiting to get the chance of execution, then in which order threads will be executed is decided by Thread Schedular.

  • We can't expect exact algorithm followed by thread schedular and it is varied from JVM to JVM.
  • Hence, we can't expect thread execution order and exact output.
  • Hence whenever situation comes to multithreading there is no guarantee for exact output, but we can provide several possible outputs.

The following are various possible outputs for the above program

Possibility-1 Possibility-2 Possibility-3 Possibility-4
Main Thread Child Thread Main Thread Child Thread
Main Thread Child Thread Child Thread Main Thread
Main Thread Child Thread Main Thread Child Thread
Main Thread Child Thread Child Thread Main Thread
Main Thread Child Thread Main Thread Child Thread
Child Thread Main Thread Child Thread Main Thread
Child Thread Main Thread Main Thread Child Thread
Child Thread Main Thread Child Thread Main Thread
Child Thread Main Thread Main Thread Child Thread
Child Thread Main Thread Child Thread Main Thread

Case 2: Difference between t.start() and t.run()

In the case of t.start(), a new Thread will be created which is responsible for the execution of run() method.
But, in this case of t.run() a new Thread won't be created and the run() method will be executed by just like a normal method call by main Thread.
Hence in the above if we replace t.start() with t.run()

//Defining a Thread
class MyThread extends Thread{
    @Override
    public void run(){
       for(int i = 0; i < 5; i++){
          System.out.println("Child Thread");
       }
    }
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread(); 
        //Thread Instantiation
        t.run();
        //Executed by Main Thread
        for(int i = 0; i < 5; i++){
            System.out.println("Main Thread");
        }
    }
}

It is executed by only Main Thread and the Output for above program is

Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread

Case 3: Importance of Thread class start() method

  • Thread class start() method is responsible to register the Thread with Thread Schedular and all other mandatory activities.
  • Hence without executing Thread class start() method there is no chance of starting a new Thread in Java.
  • Due to this, Thread class start() method is considered as heart of multithreading

start(){
  1.Register this thread with Thread Schedular;
  2.Perform all other mandatory activities;
  3.Invoke run();
}

Case 4: Overloading of run() method

Overloading of run() method is always possible but Thread class start() method can invoke no-args method.
The other overloaded run() method we have to call explicitly like a normal method call.

//Defining a Thread
class MyThread extends Thread{
    //Overloaded methods
    public void run(){
        System.out.println("no-args run");
    }
    public void run(int i){
        System.out.println("int arg run");
    }
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();
        //Thread Instantiation
        t.start();
    }
}

Output:
no-args run

Case 5: If we are not Overriding run() method

If we are not Overriding run() method then Thread class run() method will be executed which has empty implementation. Hence, we won't get any output.

//Defining a Thread
class MyThread extends Thread{
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();
        //Thread Instantiation
        t.start();
    }
}

Output:
no OUTPUT
NOTE: It is highly recommended to override run() method, otherwise don't go for Multithreading concept.

Case 6: Overriding of start() method

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

//Defining a Thread
class MyThread extends Thread{
    public void start(){
        System.out.println("start method");
    }
    public void run(){
        System.out.println("run method");
    }
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();
        //Thread Instantiation
        t.start();
        System.out.println("main method");
    }
}

Output:
start method
main method
NOTE: It is not recommended to override start() method. Otherwise, don't go for Multithreading concept.

Case 7:

//Defining a Thread
class MyThread extends Thread{
    public void start(){
        super.start();
        System.out.println("start method");
    }
    public void run(){
        System.out.println("run method");
    }
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();
        //Thread Instantiation
        t.start();
        System.out.println("main method");
    }
}

The following are various possible outputs for the above program

Possibility-1 Possibility-2 Possibility-3
run method start method start method
start method main method run method
main method run method main method

Case 8: Thread Life Cycle

Case 9:

After starting a Thread, if we are trying to restart the same thread then we will get runtime exception saying IllegalThreadStateException.

class MyThread extends Thread{
    public void run(){
        System.out.println("run method");
    }
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();
        //Thread Instantiation 
        t.start();
        System.out.println("main method");
        t.start();
    }
}

Output: main method
run method

Exception in thread "main" java.lang.IllegalThreadStateException 

at java.base/java.lang.Thread.start(Thread.java:792)

at ThreadDemo.main(ThreadDemo.java:12)

2. Defining a Thread by implementing Runnable Interface:

We can define a thread by implementing Runnable interface
1st Approach: MyThread ---------> Thread ------> Runnable(I)
2nd Approach: MyRunnable ----------------------> Runnable(I)
Runnable(I) present in java.lang package and it contains only one method i.e., run() method.

//Defining a Thread
class MyRunnable implements Runnable{
    public void run(){
        //Job of Thread
        for(int i = 0; i < 10; i++){
             System.out.println("Child Thread");
        }
    }
}
class ThreadDemo{
    public static void main(String[] args){
     MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        // r -> target runnable t.start();
        for(int i = 0; i < 10; i++){
             System.out.println("Main Thread");
        }
    }
}    

We will get mixed output and we can't tell exact output.
The following are various possible outputs for the above program

Possibility-1 Possibility-2 Possibility-3 Possibility-4
Main Thread Child Thread Main Thread Child Thread
Main Thread Child Thread Child Thread Main Thread
Main Thread Child Thread Main Thread Child Thread
Main Thread Child Thread Child Thread Main Thread
Main Thread Child Thread Main Thread Child Thread
Child Thread Main Thread Child Thread Main Thread
Child Thread Main Thread Main Thread Child Thread
Child Thread Main Thread Child Thread Main Thread
Child Thread Main Thread Main Thread Child Thread
Child Thread Main Thread Child Thread Main Thread

Case Study:

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

Case 1: t1.start()

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

Case 2: t1.run()

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

Case 3: t2.start()

A new Thread will be created which is responsible for the execution of MyRunnable class run() method.

Case 4: t2.run()

A new Thread won't be created and MyRunnable class run() method will be executed just like a normal method call.

Case 5: r.start()

we will get compile time error saying MyRunnable class doesn't have start() capability.
C-Error: Cannot find symbol method start() location class MyRunnable

Case 6: r.run()

No new Thread will be created and MyRunnable class run() method will be executed like normal method call.

Which approach is best to define a Thread?

  • Among two ways of defining a Thread, implements Runnable approach is recommended
  • In the first approach, our class always extends Thread class, there is no chance of extending any other class. Hence, we are missing inheritance benefit.
  • But in the second approach, while implementing Runnable interface, we can extends any other class. Hence, we won't miss any inheritance benefit.
  • Because of above reason implementing Runnable interface approach is recommended than extending Thread class.

Thread Class Constructors:

1.  Thread t = new Thread();
2.  Thread t = new Thread(Runnable r);
3.  Thread t = new Thread(String name);
4.  Thread t = new Thread(Runnable r, String name);
5.  Thread t = new Thread(ThreadGroup g, String name);
6.  Thread t = new Thread(ThreadGroup g, Runnable r);
7.  Thread t = new Thread(ThreadGroup g, Runnable r, String name);
8.  Thread t = new Thread(ThreadGroup g, Runnable r, String name, long stacksize);

Approach to define a Thread (Not Recommended to use):

class MyThread extends Thread{    
    public void run(){
     System.out.println("child thread");
    }    
}
class ThreadDemo{
    public static void main(String[] args){
        MyThread t = new MyThread();
        MyThread t1 = new MyThread();
        t1.start();
        System.out.println("main thread");
    }
}

Output:

Possibility1 Possibility2
child thread main thread
main thread child thread

Getting and Setting name of a Thread:

Every Thread in java has same name, it may be default name generated by JVM or customised name provided by programmer.
We can get and set name of a Thread by using the following two methods of Thread class

•   public final String getName()
•   public final void setName(String name)
class MyThread extends Thread{


}
class Test{
    public static void main(String[] args){
        System.out.println(Thread.currentThread().getName());
        //main 
        MyThread t = new MyThread(); 
        System.out.println(t.getName()); 
        // Thread-0 
        Thread.currentThread().setName("ProgrammingWithKalyan"); 
        System.out.println(Thread.currentThread().getName()); //ProgrammingWithKalyan
    }
}

Output:
main
Thread-0
ProgrammingWithKalyan

NOTE: We can get current executing Thread object by using Thread.currentThread() method

class MyThread extends Thread{
    public void run(){
        System.out.println("run method executed by Thread " + Thread.currentThread().getName());
    }
}
class Test{
    public static void main(String[] args){
        MyThread t = new MyThread();
        t.start();
        System.out.println("main method executed by Thread " + Thread.currentThread().getName());
    }
}    

Output:
run method executed by Thread Thread-0
main method executed by Thread main