Java Multithreading

Multithreading -


All of you will be familiar with multitasking, all the modern operating systems are multitasking OS. That is they can perform more than one task at the same time (concurrently) for example - while listening music you are writing something in any text editor.

Thread and Multithreading -

Similar to multitasking, multithreaded programs contains two or more parts that's can run concurrently. Each part of such a program is called Thread. And the program is called multithreaded program.And this process is called multithreading.

Difference between Multitasking and Multithreading -

Note that there is a difference between multitasking and multithreading. Multitasking means more than one process (programs) are executing concurrently for example one process or program playing any song and other program or process allows you to surf net on browser.
Whereas Multithreading means one program contains two or more parts executing at the same time for example a text editor program allows you to print and type at the same time.

Note -
Most of the computer systems contains a single processor that can process a single process at a time.
So the question arise how processor perform multiple task (process) at the same time(for example - while listening music you are writing something in any text editor).
Actually processor process one process (thread in case of multithreading) at a time and if there are multiple process ready to execute at the same time, then processor take one process, process it for some micro seconds and switches to another process and so on. And this switching from one process to another process is so fast that it looks like all the processes are running concurrently, however in reality only one process was running at a time.
This switching of processor from one process to another process is known as a Context Switching

SO what is the benefit of multitasking or multithreading then?
The basic idea of multitasking or multithreading is to make optimum utilization of processor by keeping the processor idle time to minimum. Whenever processor processing any process (thread in case of multithreading) pause for some reasons, processor does not seat idle for that time, it switches to some other process that is ready to execute and so on. In this way we keep the processor idle time to minimum.

Thread class and Runnable interface -

In java you can do multithreading by extending Thread class or implementing Runnable interface. Some important methods provided by them, which are used to process and manage threads, are as follows -
Method Meaning
getName() Obtain a thread's name.
getPriority() Obtain a thread's priority.
start() Start a thread by calling its run method.
run() Entry point for the thread.
sleep() Suspend a thread for a period of time.
join() Wait for a thread to terminate.
isAlive() Determine if a thread is still running.

The main thread -

The java programs we have done till now are controlled by a thread named main.It starts running automatically when a java program start-up.
Note that all the threads that you are going to create manually in your program are derived from main thread.
You can obtain a reference to the main thread by using a method name currentThread(). It is a public static method has the following syntax -
 public static Thread currentThread()

Once you have a reference to the main thread, you can control it just like any other thread.

Example -

public class Call {

    public static void main(String[] args) {
        
    Thread t = Thread.currentThread();
    System.out.println("Current thread name : "+t.getName());     
     
    }
}

OR

class Demo
{
    public void show()
    {
       Thread t = Thread.currentThread();
       System.out.println("Current thread name : "+t.getName());
     
    }
}
public class Call {

    public static void main(String[] args) {
        
    Demo d1 = new Demo();
    d1.show();
    }
}


Output-

Both programs will produce the same output -
Current thread name : main

Creating Thread -

There are three ways to create a thread in java -
1- using Thread class.
2- using Runnable interface.
3- using anonymous class.

1- Extending Thread class -

A class can create threads by extending thread class and override its run() method. Thread behaviour is implemented by run() methods and it forms the entire body of the thread.
We have to follow the following steps to create thread using Thread class -
  • Create a class that extends the Thread class.
  • Override the run() method of Thread class.
  • Create objects of class and call the start() method, that will start the thread. start() method automatically call the run() method, which contain code of thread.

Example -

class First extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("First "+i);
       }
     
    }
}
class Second extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Second "+i);
       }
     
    }
}
class Third extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Third "+i);
       }
     
    }
}
public class Call {

    public static void main(String[] args) {
        
        Thread t1 = new Thread(new First());
        Thread t2 = new Thread(new Second());
        Thread t3 = new Thread(new Third());
        
         t1.start();
         t2.start();
         t3.start();
        
    }
}

Output -

First Run -    First 1
               Second 1
               Second 2
               Second 3
               Second 4
               Second 5
               Third 1
               First 2
               Third 2
               First 3
               Third 3
               First 4
               Third 4
               Third 5
               First 5

Second Run -   First 1
               Second 1
               Third 1
               Second 2
               First 2
               Second 3
               Third 2
               Second 4
               First 3
               First 4
               First 5
               Second 5
               Third 3
               Third 4
               Third 5

Note that every time we will run program output may vary depending upon how switching between threads take place to best utilize CPU time.
Here we have created 3 threads t1, t2 and t3. We have no control over which threads starts first and which ends last and how switching between threads take place.
Note that if a threads pause due to switching to some other threads, it resumes its execution from where it pauses.

2- Implementing Runnable Interface -

A class can also creates threads by implementing Runnable interface. It also has run() method that defines the behaviour of thread and it has to be implemented by the class.

Example -

class First implements Runnable
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("First "+i);
       }
     
    }
}
class Second implements Runnable
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Second "+i);
       }
     
    }
}
class Third implements Runnable
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Third "+i);
       }
     
    }
}
public class Call {

    public static void main(String[] args) {
        
        Thread t1 = new Thread(new First());
        Thread t2 = new Thread(new Second());
        Thread t3 = new Thread(new Third());
        
         t1.start();
         t2.start();
         t3.start();
        
    }
}

Output -

Second 1
Third 1
First 1
Third 2
Second 2
Second 3
Third 3
First 2
Third 4
Third 5
Second 4
Second 5
First 3
First 4
First 5

3- Using Anonymous class -

We can also creates thread by using anonymous class as follows -

Example -

class First
{
    public void show()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("First "+i);
       }
     
    }
}
class Second
{
    public void show()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Second "+i);
       }
     
    }
}
class Third 
{
    public void show()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Third "+i);
       }
     
    }
}
public class Call {

    public static void main(String[] args) {
        
        Thread t1 = new Thread()
        {
            public void run()
            {
                First f = new First();
                f.show();
            }
        };
        Thread t2 = new Thread()
        {
            public void run()
            {
                Second s = new Second();
                s.show();
            }
        };
        Thread t3 = new Thread()
        {
            public void run()
            {
                Third t = new Third();
                t.show();
            }
        };
        
         t1.start();
         t2.start();
         t3.start();
        
    }
}

    

Output -

Third 1
Third 2
Third 3
Third 4
Third 5
First 1
First 2
Second 1
Second 2
Second 3
Second 4
Second 5
First 3
First 4
First 5

Thread Life Cycle

A thread can have the following states in its lifetime -
  • New Born state
  • Runnable state
  • Running state
  • Blocked state
  • Dead state
A thread can move from one state to another in different ways as shown in the below figure -

Newborn State -

When we create the object of Thread class (Thread t = new Thread()), thread will be born and called in newborn state. At this stage we can do one of the following things with thread object-
  • Schedule it for running by calling the start method (t.start()).
  • Kill it by calling the stop method (t.stop()).

Runnable State -

Thread is said to be in Runnable state when thread is ready to execute by calling start() method but does not get the processor for starting execution. i.e. waiting for the processor.

Running state -

Thread comes in Running state from Runnable state as soon as it gets the processor time and starts executing. At this stage we can do one of the following things with thread -
  • We can block a thread from running by calling the suspend() method on it. A suspended thread can be revive again by calling the notify() method on it.
  • We can make a thread sleep for a specified time period by calling the sleep(time) method. Where time will be in milliseconds. Thread will again enter the Runnable state after this specified time period.
  • We can also told a thread to wait by calling the wait() method. Thread will wait until time specified has elapsed or called the notify() or notifyAll() method. These 3 methods can be called only within a synchronised method, otherwise they will throw an exception.

Blocked State -

By calling the suspend(), wait() or sleep() method we can block a thread i.e. preventing thread from entering into running state. In blocked state thread is not dead and it is fully qualified to run again.

Dead State -

We can kill a thread in two ways - natural death and premature death. When the thread has completed its run() method then it is called natural death. But if we kill a thread by calling the stop() method then it is called premature death.

Using Thread Methods -

We can control the behaviour of thread to some extent by using thread methods such as sleep(), yield(), join(), stop().

Example -

class First extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("First "+i);
           if(i == 1)
           {
               yield();
           }
       }
     
    }
}
class Second extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Second "+i);
           if(i == 2)
           {
               stop();
           }
       }
     
    }
}
class Third extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Third "+i);
           
           if(i%2 == 0)
           {
               try
               {
               Thread.sleep(1000);
               }
               catch(Exception ex)
               {
                   
               }
           }
       }
     
    }
}
public class Call {

    public static void main(String[] args) {
        
        Thread t1 = new Thread(new First());
        Thread t2 = new Thread(new Second());
        Thread t3 = new Thread(new Third());
        
         t1.start();
         t2.start();
         t3.start();
        
    }
}

Output -

First 1
Second 1
Second 2
Third 1
Third 2
First 2
First 3
First 4
First 5
Third 3
Third 4
Third 5

Observe the output -
First of all t1 thread starts execution and enter the running state. It runs only one time and processor switch over thread t2.
This is because we have called the yield() method on t1 when i became 1, so it will send t1 to runnable state from running state.
t2 runs 2 times and then never executes again because we have called stop() method and it will kill the thread t2.
Next it switch over to thread t3 and runs 2 time and again switch over to other thread, this is because we have called sleep() method on t3 after every 2 runs, So it will send the t3 into runnable state from running state.
Note that sleep() method may throw an InterruptedException and it is a checked exception so we have to handle it.
Also note that we cannot completely control the thread behaviour of threads it may produce some other output in second run.

Thread Priority -

In java each thread is assigned a priority, which determines the order of execution of threads. Thread priority determines the relative priority of one thread over another thread.
Thread priority is an integer value that may vary from 1 to 10. Thread class define 3 priority constants-
MIN_PRIORITY 1
NORM_PRIORITY 5
MAX_PRIORITY 10
We can set the priority of any thread by using the setPriority(int) method as follows -
threadObject.setPriority(int);
Note - If we do not define any thread priority then by default every thread has the NORM_PRIORITY by default.
If two or more thread have the same priority then on Windows OS they will be treated by round robin scheduling algorithm.
We can also get the priority of any thread by using the getPriority() method as follows -
threadObject.getPriority();

Example -

class First extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("First "+i);
         
       }
     
    }
}
class Second extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Second "+i);
       
       }
     
    }
}
class Third extends Thread
{
    public void run()
    {
       for(int i=1;i<=5;i++)
       {
           System.out.println("Third "+i);
          
       }
     
    }
}
public class Call {

    public static void main(String[] args) {
        
        Thread t1 = new Thread(new First());
        Thread t2 = new Thread(new Second());
        Thread t3 = new Thread(new Third());
       
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(t1.getPriority()+1);
        
         t1.start();
         t2.start();
         t3.start();
        
    }
}

Output -

First 1
Second 1
Second 2
Second 3
Third 1
Second 4
Second 5
Third 2
Third 3
Third 4
Third 5
First 2
First 3
First 4
First 5
Note that t1 thread start first but it is prompted by t2 because t2 has higher priority than t1 and later on t3 has been assigned the highest priority so it takes control over the t1 and t2.
Always remember that thread priority also depends upon several other OS factors too. So assigning highest priority to any thread does not guarantee that it will run first.

Synchronization -

If two or more threads wants to access to a shared resource at the same time and the recourse is such that it may produce some incorrect result if accessed concurrently(for example one thread try to read a record from a file while another is still writing to the same file ). In such case there should be some way to ensure that only one thread can access the resource at the same time. The process by which this is achieved is known as synchronization.

We can synchronize any resource (code) in two ways -

1- Synchronizing methods.
2- Synchronizing statements (objects).
First of all let's look at what problems may occur if multiple threads want to access a shared resource. Consider the following example -
class Test implements Runnable
{
    private int points =10;
    public void run()
    {
        calculatePoints();
    }
    
    public void calculatePoints()
    {
        if(points > 0)
        {
        System.out.println("You got 5 points");
        points = points - 5;
        System.out.println("Points left :"+points);
        }
        else
        {
            System.out.println("No points left");
        }
    }
}
public class Call {

    public static void main(String[] args) {
        
        Test tst = new Test();
        Thread t1 = new Thread(tst);
        Thread t2 = new Thread(tst);
        Thread t3 = new Thread(tst);
               
         t1.start();
         t2.start();
         t3.start();
              
    }
}

    

Output -

You got 5 points
You got 5 points
You got 5 points
Points left :0
Points left :-5
Points left :-5

    
Note that we have 10 points in total but it prints "You got 5 points" 3 times, i.e. 15 points.
This is because all the threads are accessing the calculatePoints() method concurrently and while it was accessed by one thread, in the middle, the second thread pre-empted the first one and so on. That is why it is giving incorrect results.
So in such situation it is necessary that if one thread is accessing a shared resource, then no other thread should be able to access that resource until it was free by first one. And in java it is archived by synchronization.
Consider the following example -

1- Synchronizing methods -

class Test implements Runnable
{
    private int points =10;
    public void run()
    {
        calculatePoints();
    }
    
 synchronized public void calculatePoints()
    {
        if(points > 0)
        {
        System.out.println("You got 5 points");
        points = points - 5;
        System.out.println("Points left :"+points);
        }
        else
        {
            System.out.println("No points left");
        }
    }
}
public class Call {

    public static void main(String[] args) {
        
        Test tst = new Test();
        Thread t1 = new Thread(tst);
        Thread t2 = new Thread(tst);
        Thread t3 = new Thread(tst);
               
         t1.start();
         t2.start();
         t3.start();
              
    }
}

    

Output -

You got 5 points
Points left :5
You got 5 points
Points left :0
No points left

    
In this case if thread t1 is accessing the method first then it cannot be accessed by t2 or t3 until it has been freed by t1.i.e only one thread can access the method at a time.

2- Synchronizing statements (objects) -

class Demo
{
  private int points = 10;
  public void calculatePoints()
  {
   if(points > 0)
   {
    System.out.println("You got 5 points");
    points = points - 5;
    System.out.println("Points left :"+points);
   }
   else
   {
    System.out.println("No points left");
   }
  }
}


class Test implements Runnable
{
  Demo d1 = new Demo();

  public void run()
  {
   synchronized(d1)
   {
    d1.calculatePoints();
   }
 }

}


public class Call {

 public static void main(String[] args) {

  Test tst = new Test();
  Thread t1 = new Thread(tst);
  Thread t2 = new Thread(tst);
  Thread t3 = new Thread(tst);

  t1.start();
  t2.start();
  t3.start();

  }
}
    

Output -

You got 5 points
Points left :5
You got 5 points
Points left :0
No points left