Open In App

Inter-thread Communication in Java

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Share
Report issue
Report

Prerequisite: Multithreading in Java, Synchronized in Java 

Inter-thread communication in Java is a mechanism in which a thread is paused running in its critical section and another thread is allowed to enter (or lock) in the same critical section to be executed.

Note: Inter-thread communication is also known as Cooperation in Java.

What is Polling, and what are the problems with it? 

The process of testing a condition repeatedly till it becomes true is known as polling. Polling is usually implemented with the help of loops to check whether a particular condition is true or not. If it is true, a certain action is taken. This wastes many CPU cycles and makes the implementation inefficient. 

For example, in a classic queuing problem where one thread is producing data, and the other is consuming it.

How Java multi-threading tackles this problem? 

To avoid polling, Java uses three methods, namely, wait(), notify(), and notifyAll(). All these methods belong to object class as final so that all classes have them. They must be used within a synchronized block only. 

  • wait(): It tells the calling thread to give up the lock and go to sleep until some other thread enters the same monitor and calls notify().
  • notify(): It wakes up one single thread called wait() on the same object. It should be noted that calling notify() does not give up a lock on a resource.
  • notifyAll(): It wakes up all the threads called wait() on the same object.

Example:

A simple Java program to demonstrate the three methods. Please note that this program might only run in offline IDEs as it contains taking input at several points.

Java




// Java program to demonstrate inter-thread communication
// (wait(), join() and notify())
  
import java.util.Scanner;
  
public class threadexample
{
    public static void main(String[] args) throws InterruptedException
    {
        final PC pc = new PC();
  
        // Create a thread object that calls pc.produce()
        Thread t1 = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    pc.produce();
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        });
  
        // Create another thread object that calls
        // pc.consume()
        Thread t2 = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    pc.consume();
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        });
  
        // Start both threads
        t1.start();
        t2.start();
  
        // t1 finishes before t2
        t1.join();
        t2.join();
    }
  
    // PC (Produce Consumer) class with produce() and
    // consume() methods.
    public static class PC
    {
        // Prints a string and waits for consume()
        public void produce()throws InterruptedException
        {
            // synchronized block ensures only one thread
            // running at a time.
            synchronized(this)
            {
                System.out.println("producer thread running");
  
                // releases the lock on shared resource
                wait();
  
                // and waits till some other method invokes notify().
                System.out.println("Resumed");
            }
        }
  
        // Sleeps for some time and waits for a key press. After key
        // is pressed, it notifies produce().
        public void consume()throws InterruptedException
        {
            // this makes the produce thread to run first.
            Thread.sleep(1000);
            Scanner s = new Scanner(System.in);
  
            // synchronized block ensures only one thread
            // running at a time.
            synchronized(this)
            {
                System.out.println("Waiting for return key.");
                s.nextLine();
                System.out.println("Return key pressed");
  
                // notifies the produce thread that it
                // can wake up.
                notify();
  
                // Sleep
                Thread.sleep(2000);
            }
        }
    }
}


Output

producer thread running
Waiting for return key.
Return key pressed
Resumed

Explanation:

As monstrous as it seems, it is a piece of cake if you go through it twice. 

  1. In the main class, a new PC object is created.
  2. It runs produce and consume methods of PC objects using two different threads, namely t1 and t2, and waits for these threads to finish.

Let’s understand how our produce and consume method works. 

  • First of all, the use of a synchronized block ensures that only one thread at a time runs. Also, since there is a sleep method just at the beginning of consume loop, the producing thread gets a kickstart.
  • When the wait is called in produce method, it does two things. Firstly it releases the lock it holds on the PC object. Secondly, it makes the produce thread go on a waiting state until all other threads have terminated. It can again acquire a lock on a PC object, and some other method wakes it up by invoking notify or notifyAll on the same object.
  • Therefore we see that as soon as the wait is called, the control transfers to consume thread, and it prints -“Waiting for return key.”
  • After we press the return key, consume method invokes notify(). It also does two things- Firstly, unlike wait(), it does not release the lock on shared resources therefore for getting the desired result, it is advised to use notify only at the end of your method. Secondly, it notifies the waiting threads that they can now wake up but only after the current method terminates.
  • As you might have observed that even after notifying, the control does not immediately pass over to the produce thread. The reason for it is that we have called Thread.sleep() after notify(). We already know that the consume thread is holding a lock on a PC object. Another thread cannot access it until it has released the lock. Hence only after the consume thread finishes its sleep time and after that terminates by itself, the produce thread cannot take back the control.
  • After a 2 second pause, the program terminates to its completion.

If you are still confused as to why we have used notify in consume thread, try removing it and running your program again, as you must have noticed now that the program never terminates. 

The reason for this is straightforward. When you called to wait on the produce thread, it went on waiting and never terminated. Since a program runs till all its threads have terminated, it runs on and on. 

There is a second way around this problem. You can use a second variant of wait(). 

void wait(long timeout) 

This would make the calling thread sleep only for a time specified. 



Last Updated : 25 Jun, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads