Inter Thread Communication in Java

Overview
Inter Thread Communication, as the name implies, is a method that allows many synchronized threads to communicate or interact with one another. In Java, there are two ways to implement inter-thread communication: using wait() and notify() methods and using the higher-level constructs of the java.util.concurrent package.
NOTE: Inter Thread Communication is also known as Cooperation in Java.
Introduction to Inter Thread Communication in Java
When a program is executed, it becomes a process, which can be further divided into threads. Threads are independent paths of execution within the same process, and they allow a program to perform multiple tasks simultaneously, which can improve performance and efficiency.
Inter-Thread Communication (Cooperation) is a mechanism that allows threads to exchange information or coordinate their execution. It enables threads to work together to solve a common problem or to share resources. ITC can involve synchronization mechanisms such as mutexes or semaphores, which ensure that critical sections of code are executed by only one thread at a time to avoid concurrency issues.
What Is Polling And What Are The Problems With It?
Polling is the process of continually testing a condition until it becomes true. Polling is usually done with the use of multiple loops, and it determines whether a condition is true or false. When we discover that a certain condition is true, we take a specific action. However, because this method uses numerous CPU cycles, it is wasteful, thus we employ inter thread communication to overcome the problem.
To avoid polling, Java provides three methods. These are the wait(), notify(), and notifyAll methods(). Because these methods are in the object class and are marked as final, they can be used in any class. They can only be utilized inside a synchronized block.
Wait() Method
Let's say we are currently running Thread1 and we want to run Thread2. Since inter thread communication can be done in a synchronized block only one thread can run at a time. So to run Thread2 we must "pause" Thread1. The wait() function helps us achieve this exact thing.
The wait() method aids inter thread communication by releasing the lock on the current or calling thread (Thread1 in the above example) and instructing it to sleep until another thread (Thread2 in the above example) enters the monitor and calls notify() or notifyAll(), or until a certain period of time has passed.
The current thread must own this object's monitor, hence the wait() function must be used from the synchronized method only; otherwise, an error will be thrown.
Syntax:
Method | Description |
---|---|
public final void wait()throws InterruptedException | It waits until the object is notified. |
public final void wait(long timeout)throws InterruptedException | It waits for the specified amount of time. |
Notify() Method
Now let's say after we completed our work in Thread2 and we want to return to Thread1. To do so, we need to inform Thread1 that it can now use the object to run itself. We can use the notify() method to do so.
NOTE: Why is Thread1 waiting for the object?
- Because we used the wait() function in Thread1 and since only one thread can run at a time Thread1 is waiting for the object to run.
The notify method() aids inter thread communication by allowing a single thread that is waiting for the object's monitor to become a runnable thread by waking it up.
Note: The decision of which waiting thread to awaken is arbitrary and is determined by the implementation.
Syntax:
NotifyAll() Method
In a similar way to the notify() method, the notifyAll() method aids inter thread communication. The only difference is that it wakes up all of the object monitor waiting for threads, making them all runnable at the same time.
Note: The term "runnable" does not imply that the thread is currently running. It signifies the thread can obtain a lock before proceeding to execution.
Syntax:
Example to Demonstrate Inter Thread Communication
OUTPUT:
Explanation:
In the main class, we create a new Game object. It runs csgo and valorant methods on two threads, t1 and t2, and then waits for them to finish. Let's look at how our csgo and valorant methods function together.
- First and foremost, using a synchronized block ensures that only one thread is active at any given time. The csgo thread also gets a kickstart because there is a sleep method right at the start of the consume loop.
- When we use the csgo method wait(), it performs two functions. It begins by releasing the lock it has on the Game object. Second, it puts the csgo thread in a waiting state until all other threads are finished. It can regain control of a Game object by using notify() or notifyAll() on the same object, if some other function wakes it up.
- As a result, as soon as the wait() function is invoked, the control transfers to valorant thread and prints -"Waiting for return key csgo paused valorant running." to show that the csgo thread is in a waiting state while the valorant thread is active.
- The valorant method calls notify() once we press the return key. It also accomplishes two goals: To begin with, unlike wait(), it does not release the lock on shared resources, therefore it's best to use notify just at the end of your method to get the desired outcome. Second, it informs the waiting threads that they are now free to wake up but only once the current procedure has finished.
- As you may have noticed, control does not instantly pass to the csgo thread even after notify(). The reason for this is that after notify(), we called Thread.sleep() . The valorant thread has a lock on a Game object, as we already know. It cannot be accessed by another thread until the lock has been released. As a result, the csgo thread can only reclaim control after the valorant thread has completed its sleep period and then terminated by itself.
- After a 2-second delay, the program finishes at the end.
Note If we delete the notify() call from the valorant thread and run the program again, we will observe that it no longer terminates.
The reason behind this is easy to understand. When you asked for the csgo thread to wait, it just sat there waiting and never ended. A program continues to operate until all of its threads have ended.
To avoid this, we can use wait(long timeout), which accepts a timeout parameter and terminates the program when the csgo thread has waited for the specified amount of time.
Difference Between Wait() and Sleep()
The wait() and sleep() methods are very different from each other.
wait() | sleep() |
---|---|
It is found in Object class | It found in Thread class |
It is the non-static method | It is the static method |
The wait() method releases the lock. | The sleep() method doesn't release the lock. |
It should be notified by notify() or notifyAll() methods if no timeout argument is given | After the specified amount of time, sleep is completed. |
Cooperation Using the java.util.concurrent
The java.util.concurrent package provides higher-level constructs such as locks, semaphores, and condition variables to facilitate inter-thread communication and synchronization.
One such construct is the CyclicBarrier class, which allows a group of threads to wait at a barrier until all threads have reached that point. To use the CyclicBarrier class, you create an instance of it with the number of threads to wait for, and then call the await() method in each thread.
Let's see an example implementation:
OUTPUT :
The CyclicBarrier class allows a group of threads to wait at a barrier until all threads have reached that point. In this example, we create a CyclicBarrier instance with a count of 2, which means that the two threads will wait at the barrier until both threads have called the await() method. The ThreadA class waits at the barrier by calling await(), while the ThreadB class waits for 2 seconds before calling await(). When both threads have called the await() method, they are released from the barrier and resume their execution.
Conclusion
- Inter thread communication also known as Cooperation is the method using which synchronized threads can communicate with each other.
- Polling uses a lot of CPU usage so we try to stop it.
- To stop polling we use 3 different methods: wait(), notify(), notifyAll().
- The java.util.concurrent package provides higher-level constructs such as locks, semaphores, and condition variables to facilitate inter-thread communication and synchronization.