Skip to main content

Threads in Java - Inter-Thread Communication (Part 5)

Sometimes in our applications, we require two or more threads to communicate with each other. One such most common use case is where we have a queue from which one thread is consuming data and the other data is producing data and putting in the queue.

Here we have to check if the queue has some data before consuming it. The straightforward method to achieve this polling. In polling, we check if the queue is filled with some data in a loop. If the queue is not empty, we take some action to consume data. This method is inefficient as it wastes CPU cycles.

Inter-Thread Communication

To avoid polling, Java has a mechanism called inter-thread communication in which one thread sends a notification to the other thread(s) once a certain condition is met. This mechanism has three methods, all of which are in the java.lang.Object class - 
  • wait() - This method pauses the thread and lets it do nothing while a certain condition is true. For e.g., in our producer-consumer problem, the consumer thread has to wait until there is some element in the queue. On the other hand, the producer thread has to wait till the time the queue is full.
  • notify() - This method wakes up only one thread waiting on the object and that threads start running. If there is more than one thread waiting on the object, this method wakes up only one of them. But which thread will be woken up depends on the OS implementation.
  • notifyAll() - This method wakes up all the threads waiting on the object. But which one will be processed first, depends entirely on the OS implementation.

Producer Consumer problem

This problem demonstrates an excellent use case of wait() and notify()/notifyAll() methods. This problem has the following constraints
  • You are given a queue of a specified size in which value can be added as well values can be removed from it.
  • The thread which adds the value is called producer. A producer can only add a value if the queue is not full, i.e. the size of the queue < max size.
  • The thread which consumes the value is called consumer. A consumer can only consume from the queue if the queue is not empty, i.e. the size of the queue != 0.
  • If the queue is full, then the producer needs to wait and communicate the consumer to consume from the queue.
  • If the queue is empty, then the consumer has to wait and communicate the producer to add some values in the queue.
  • This communication is achieved via wait() and notify()/notifyAll() methods. Below code demonstrates how - 
Here the producer waits when the queue is full and notifies the consumer to consumer value from the queue after adding some value to it. On the other hand, the consumer waits when the queue is empty and notifies the producer after consuming a value. The above code will have the following output -

Queue is empty!
Produced value: 84
Produced value: 69
Produced value: 5
Produced value: 15
Queue is full!
Consumer value: 84
Consumer value: 69
Consumer value: 5
Consumer value: 15

Calling wait() & notify()/notify() from if or while block

If we call wait() inside the if block, it may lead to some errors because it's possible for a thread to wake up spuriously even when waiting condition is not changed. 
If we don't check the condition again after waking up by using a loop, we might take the wrong action which may cause problem e.g. trying to insert item on a full queue or trying to consume from an empty queue. That's why we should always call wait and notify method from a loop and not from if block.

Why should we call wait(), notify(), notifyAll() from synchronized block/method?

In our producer-consumer problem, when we call notify()/notifyAll(), a notification is issued to a single/multiple threads that the waiting condition is changed. Once the notification leaves the synchronized block, all the threads fight for the lock on which they are waiting. One lucky thread returns from the wait() and proceeds further.

Let's divide the whole scenario into steps
  1. Producer tests the condition (is queue full?) and decides it must wait (if the queue is full).
  2. The consumer sets condition after consuming an element from the queue.
  3. Consumer calls notify() but this goes unheard as the Producer is not yet waiting.
  4. Producer thread calls the wait() and now goes in the waiting state.
We can clearly see a race condition here which causes us to lose a notification and this will give us the wrong result.

Thus, it is always a good practice to acquire the lock of the object before calling these methods. Since the wait() method also releases the lock prior to waiting and reacquires the lock prior to returning from the wait() method, we must use this lock to ensure that checking the condition (queue is full or not) and setting the condition (taking element from queue) is atomic which can be achieved by using synchronized method or block.

Difference between notify() and notifyAll()

When we call notify() only one of the waiting thread wakes up but it is not guaranteed which thread will be woken up. While if we call notifyAll(), all threads waiting on the lock will be woken up and then fight to acquire the lock. 

This is the reason wait() is called on the loop so that if multiple threads are woken up, the thread which will get the lock first execute and it may reset the waiting condition, which will force the subsequent threads to wait.

Following example shows that only one thread is woken up when we call notify() and all threads are woken up when we call notifyAll() -

In this example three threads will wait if boolean variable go is false, remember boolean go is a volatile variable so that all threads will see its updated value. Initially, three threads W1, W2, W3 will wait because variable go is false than one thread N1 will make go true and notify all threads by calling notifyAll() method or notify() just one thread by calling notify() method.

In the case of notify() call, there is no guarantee which thread will wake up and we can see it by running this program multiple times.

In the case of notifyAll(), all thread will wake up but they will compete for monitor or lock and the Thread which will get the lock first will finish its execution and resetting go to false which will force other two threads still waiting.

At the end of this program, we will have two threads waiting and two threads including notification thread finished. The program will not terminate because the other two threads are still waiting and they are not daemon threads.

Output in case of notify() call

Thread[W1,5,main] is going to wait on this object
Thread[W2,5,main] is going to wait on this object
Thread[W3,5,main] is going to wait on this object
Thread[N1,5,main] is going to notify all the threads waiting on this object
Thread[W1,5,main] is woken up
Thread[W1,5,main] finished execution
Thread[N1,5,main] finished execution

Output in case of notifyAll() call

Thread[W1,5,main] is going to wait on this object
Thread[W3,5,main] is going to wait on this object
Thread[W2,5,main] is going to wait on this object
Thread[N1,5,main] is going to notify all the threads waiting on this object
Thread[N1,5,main] finished execution
Thread[W1,5,main] is woken up
Thread[W1,5,main] finished execution
Thread[W2,5,main] is woken up
Thread[W2,5,main] is going to wait on this object
Thread[W3,5,main] is woken up
Thread[W3,5,main] is going to wait on this object

Conclusion

In this post, we discussed inter thread communication using wait(), notify() and notifyAll() methods. We also discussed the required conditions to call these methods with reasons. The code can be found in the commit on my GitHub.

Feel free to befriend me on FacebookTwitter or Linked In or say Hi by email.

Happy Coding 😊 and Happy Learning 😊

Comments

Popular posts from this blog

Parsing XML using Retrofit

Developing our own type-safe HTTP library to interface with a REST API can be a real pain as we have to handle many aspects -
making connectionscachingretrying failed requeststhreadingresponse parsingerror handling, and more.  Retrofit, on the other hand, is a well-planned, documented and tested library that will save you a lot of precious time and headaches. In this tutorial, we are going to discuss how we can parse the XML response returned from https://timesofindia.indiatimes.com/rssfeedstopstories.cms using the Retrofit library.

To work with Retrofit, we need three classes -  Model class to map the JSON dataInterfaces which defines the possible HTTP operationsRetrofit.Builder class - Instance which uses the interface and the Builder API which allows defining the URL endpoint for the HTTP operation. Every method of the above interface represents on possible API call. The request type is specified by using appropriate annotations (GET, POST). The response is returned as a Call object…

Threads in Java - Masterclass (Part 0)

Multithreading is a way to introduce concurrency in a program. In any case, if there are parallel paths in our program (parts which do not depend on the result from another part), we can make use of multithreading.
One should exploit this feature, especially with all these multiple core machines nowadays.

Below are a few reasons why we should use multithreading -
1. Keep a process responsive There was once a time when you would print a document in MS Word and the application would freeze for an annoyingly long amount of time until the job finished. Eventually, Microsoft solved this problem by running a printing job parallel to the main thread/ GUI thread.  To be clear though, not only GUI apps but Network services have to keep an ear to the ground for new clients, dropped connections and cancellation requests. In either case, it is critical to do the heavy lifting on a secondary thread to keep the user satisfied. 2. Keep a processor busy Keeping a processor busy can be a tough task e…

Material design profile page in Android

Hey everyone, some days back I was working on one my personal Android project. In that project, I was supposed to create a simple profile page for a user. This profile page was supposed to show some basic details of a user.

The output of this UI will be like this -
I created the profile page using material design and in this post, I am going to discuss a step by step tutorial to create a simple yet elegant profile page. Without further ado, let's get started.
Creating a new project Click on File ➤ New Project ➤ Empty Activity and fill the necessary details. Change styles.xml fileNavigate to app\src\main\res\values\styles.xmlChange the style value from DarkActionBar to NoActionBar as below<resources><!-- Base application theme. --><stylename="AppTheme"parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><itemname="colorPrimary">@color/colorPrimary</item><itemname="colorPrimaryDark&qu…