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


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 😊


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 connections caching retrying failed requests threading response parsing error 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  using the Retrofit library. To work with Retrofit, we need three classes -  Model class to map the JSON data Interfaces which defines the possible HTTP operations Retrofit.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 respon

Threads in Java - CountDownLatch (Part 12)

A CountDownLatch is a synchronizer which allows one thread to wait for one or more threads before starts processing. A good application of  CountDownLatch is in Java server-side applications where a thread cannot start execution before all the required services are started. Working A  CountDownLatch is initialized with a given count which is the number of threads it should wait for. This count is decremented by calling countDown() method by the threads once they are finished execution. As soon as the count reaches to zero, the waiting task starts running. Code Example Let us say we require three services, LoginService, DatabaseService and CloudService to be started and ready before the application can start handling requests. Output Cloud Service is up! Login Service is up! Database Service is up! All services are up. Now the waiting thread can start execution. Here, we can see that the main thread is waiting for all the three services to start before starting its own

Threads in Java - Masterclass (Part 0)

Threads in Java 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 proc