Sunday, September 17, 2023

Inter Thread Communication in Java using wait() and notify() - Example Tutorial

Wait and notify methods in Java are used for inter-thread communication i.e. if one thread wants to tell something to another thread, it uses notify() and notifyAll() method of java.lang.Object. A classical example of the wait and notify method is a Producer-Consumer design pattern, where One thread produces and put something on the shared bucket, and then tell the other thread that there is an item for your interest in a shared object, consumer thread than pick than item and do his job, without the wait() and notify(), consumer thread needs to be busy checking, even if there is no change in the state of the shared object.

This brings an interesting point on using the wait and notifies mechanism, a call to notify() happens when the thread changed state of the shared object i.e. in this case producer change bucket from empty to not empty, and consumer change state from non-empty to empty.

Also, the wait and notify method must be called from a synchronized context, wondering why to read this link for some reason which makes sense. Another important thing to keep in mind while calling them is, using a loop to check conditions instead of if block.

This is really tricky for beginners, which often don't understand difference and wonders why wait and notify get called form loops. Joshua Bloch has a very informative item in his book Effective Java, I strongly suggest reading that.

And, if you are serious about mastering Java multi-threading and concurrency then I also suggest you take a look at the Java Multithreading, Concurrency, and Performance Optimization course by Michael Pogrebinsy on Udemy. It's an advanced course to become an expert in Multithreading, concurrency, and Parallel programming in Java with a strong emphasis on high performance

In short, a waiting thread may wake up, without any change in it's waiting for the condition due to spurious wakeup.

For example, if a consumer thread, which is waiting because the shared queue is empty, gets wake up due to a false alarm and tries to get something from the queue without further checking whether the queue is empty or not then the unexpected result is possible. Here is the standard idiom for calling wait, notify, and notifyAll methods in Java :





How to call wait method in Java? Example

synchronized (object) {
         while ()
             object.wait();
         ... // Perform action appropriate to condition
 }

and here is a complete example of calling the wait and notify method in Java using two threads, producer and consumer

wait notify example in Java using inter thread communication





Java Inter Thread Communication Example using wait() and notify() methods

Java inter thread communication example wait notify methodWe have a shared Queue and two threads called Producer and Consumer. Producer thread puts numbers into a shared queue and Consumer thread consumes numbers from the shared buckets.

 Condition is that once an item is produced, the consumer thread has to be notified, and similarly after consumption producer thread needs to be notified. This inter-thread communication is achieved using the wait and notify method. 

Remember wait and notify method is defined in the object class, and they are must be called inside the synchronized block.

package concurrency;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.log4j.Logger;

public class InterThreadCommunicationExample {

    public static void main(String args[]) {

        final Queue sharedQ = new LinkedList();

        Thread producer = new Producer(sharedQ);
        Thread consumer = new Consumer(sharedQ);

        producer.start();
        consumer.start();

    }
}

public class Producer extends Thread {
    private static final Logger logger = Logger.getLogger(Producer.class);
    private final Queue sharedQ;

    public Producer(Queue sharedQ) {
        super("Producer");
        this.sharedQ = sharedQ;
    }

    @Override
    public void run() {

        for (int i = 0; i < 4; i++) {

            synchronized (sharedQ) {
                //waiting condition - wait until Queue is not empty
                while (sharedQ.size() >= 1) {
                    try {
                        logger.debug("Queue is full, waiting");
                        sharedQ.wait();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                logger.debug("producing : " + i);
                sharedQ.add(i);
                sharedQ.notify();
            }
        }
    }
}

public class Consumer extends Thread {
    private static final Logger logger = Logger.getLogger(Consumer.class);
    private final Queue sharedQ;

    public Consumer(Queue sharedQ) {
        super("Consumer");
        this.sharedQ = sharedQ;
    }

    @Override
    public void run() {
        while(true) {

            synchronized (sharedQ) {
                //waiting condition - wait until Queue is not empty
                while (sharedQ.size() == 0) {
                    try {
                        logger.debug("Queue is empty, waiting");
                        sharedQ.wait();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                int number = sharedQ.poll();
                logger.debug("consuming : " + number );
                sharedQ.notify();
              
                //termination condition
                if(number == 3){break; }
            }
        }
    }
}

Output:
05:41:57,244 0    [Producer] DEBUG concurrency.Producer  - producing : 0
05:41:57,260 16   [Producer] DEBUG concurrency.Producer  - Queue is full, waiting
05:41:57,260 16   [Consumer] DEBUG concurrency.Consumer  - consuming : 0
05:41:57,260 16   [Consumer] DEBUG concurrency.Consumer  - Queue is empty, waiting
05:41:57,260 16   [Producer] DEBUG concurrency.Producer  - producing : 1
05:41:57,260 16   [Producer] DEBUG concurrency.Producer  - Queue is full, waiting
05:41:57,260 16   [Consumer] DEBUG concurrency.Consumer  - consuming : 1
05:41:57,260 16   [Consumer] DEBUG concurrency.Consumer  - Queue is empty, waiting
05:41:57,260 16   [Producer] DEBUG concurrency.Producer  - producing : 2
05:41:57,260 16   [Producer] DEBUG concurrency.Producer  - Queue is full, waiting
05:41:57,260 16   [Consumer] DEBUG concurrency.Consumer  - consuming : 2
05:41:57,260 16   [Consumer] DEBUG concurrency.Consumer  - Queue is empty, waiting
05:41:57,260 16   [Producer] DEBUG concurrency.Producer  - producing : 3
05:41:57,276 32   [Consumer] DEBUG concurrency.Consumer  - consuming : 3


That's all on this simple example of Inter thread communication in Java using the wait and notify method. You can see that both Producer and Consumer threads are communicating with each other and sharing data using shared Queue, Producer notifies consumer when there is an item ready for consumption, and Consumer thread tells Producer once it's done with consuming.

This is a classical example of the Producer-Consumer design pattern as well, which inherently involves inter-thread communication and data sharing between threads in Java.



Other Java Multithreading and Concurrency Articles you may like
  • The Java Developer RoadMap (roadmap)
  • 5 Courses to Learn Java Multithreading in-depth (courses)
  • Difference between atomic, synchronized and volatile in Java (answer)
  • 6 Concurrency Books Java developer can read (books)
  • What is happens before in Java Memory model? (answer)
  • Difference between Cyclicbarrier and CountDownLatch in Java? (answer)
  • Top 5 Books to Master Concurrency in Java (books)
  • 10 Java Multithreading and Concurrency Best Practices (article)
  • 50+ Thread Interview Questions for Beginners (questions)
  • Understanding the flow of data and code in Java program (answer)
  • Is Java Concurrency in Practice still valid? (answer)
  • 10 Tips to become a better Java Developer (tips)
  • 10 Advanced books for Experienced Programmers (books)
  • Top 5 skills to Crack Coding interviews (article)
  • 10 Advanced Core Java Courses for Experienced Programmers (courses)

Thanks for reading this article so for. If you like the Java Multithreading tutorial, then please share it with your friends and colleagues. If you have any questions or feedback, then please drop a note.

P. S. - If you are looking for a free Java multithreading course to the master thread concepts then I also suggest you check out this Java Multithreading free course on Udemy. It's completely free and all you need is a free Udemy account to join this course.

Now, over to you, what is the best way for inter thread communication in Java? by using wait and notify method or by using Queue data structure like BlockingQueue?

13 comments :

Anonymous said...

Good article.
Is it same for all JDK versions ?

Brooks DuBois said...

I had to add to the queues but for the most part yes. The log4j dependency is annoying... Just control-f "logger.debug" and replace with "System.out.println"

Anonymous said...

@Anonymous, Yes, Wait and notify method are available from JDK 1.5 so above code of inter thread communicaiton will work on all JDK versions.

Anonymous said...

sir its difficult program...please upload simple program for beginers

Anonymous said...

This code returns different results on different runs.
Also it is interesting when you add multiple Consumer classes...try it out.

Anonymous said...

Hint: When using multiple Consumer classes, you should notify more then one ;-)

Dharam said...

Do we really need while loon inside synscronized context? I think 'If' will suffice here. Anyways top loop is for loop, so it's definitely run until 4 elements are produced/consumed.

Example : if(sharedQ.size==) --> sharedQ.wait();

Anonymous said...

I have a query, if you can please help:
Once 'Producing 0' is printed and line sharedQ.notify() is executed in producer synchronized block, how come Producer synchronized block again acquires the lock on sharedQ and prints 'Queue is full, waiting'. As per my understanding, as soon as notify() is trigerred control should come to waiting thread(in this consumer thread) for acquiring lock on same object. But rather Ouput is: 'Producing 0', then 'Queue is full, waiting' and then comes 'Consuming 0'.
Same is applicable when Concumer thread triggers sharedQ.notify() how come line 'Queue is empty, waiting' line is getting printed?
Can you please help??

Anonymous said...

Here is the simple program :-)

class Customer{
int amount=10000;

synchronized void withdraw(int amount){
System.out.println("going to withdraw...");

if(this.amount<amount){
System.out.println("Less balance; waiting for deposit...");
try{wait();}catch(Exception e){}
}
this.amount-=amount;
System.out.println("withdraw completed...");
}

synchronized void deposit(int amount){
System.out.println("going to deposit...");
this.amount+=amount;
System.out.println("deposit completed... ");
notify();
}
}

class Test{
public static void main(String args[]){
final Customer c=new Customer();
new Thread(){
public void run(){c.withdraw(15000);}
}.start();
new Thread(){
public void run(){c.deposit(10000);}
}.start();

}}

Output: going to withdraw...
Less balance; waiting for deposit...
going to deposit...
deposit completed...
withdraw completed

Anonymous said...

@Here is the simple program

what happen when your waiting thread in withdraw wake up prematurely and not because of deposit notify it.

To put it another way, a thread may wake up from wait() for no reason at all - with no notify(0 or notifyAll(). So you should always have a loop to check whether whatever-thing-you're-waiting-for has actually occurred yet or not, and if not, resume the wait().

https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Prudhvi said...

Hi,
I am still confusing on wait and notify usage.
Could you please clarify my below doubt:::

What will happen if we call notify, when there is no thread in waiting state.

package com.concurrent.byp;

public class ThreadA extends Thread {

public static void main(String[] args) throws InterruptedException {
ThreadB b = new ThreadB();
b.start();
int count = 0;
for(int i=0;i<1;i++){
System.out.println("count::"+count);
count= count+i;
}
synchronized (b) {
System.out.println("inside ThreadA");
b.wait();
System.out.println("end of ThreadA::");

}
}
}





And another class B:::::
package com.concurrent.byp;

public class ThreadB extends Thread {
int count = 0;
@Override
public void run() {

synchronized (this) {
System.out.println("start of ThreadB");
notify();
System.out.println("end of ThreadB");

}
}

}




=> when I try to run ThreadA as java application, in few cases I am seeing java execution is not terminating. and in few cases java execution is terminating.

for java execution not terminating cases::and looks fine for me.
case1:
start of ThreadB
end of ThreadB
count::0
inside ThreadA


Java execution terminating cases::
Q)but I am expecting execution should not terminate in below case:??????
count::0
start of ThreadB
end of ThreadB
inside ThreadA
end of ThreadA::


case2::
start of ThreadB
count::0
end of ThreadB
inside ThreadA
end of ThreadA::


Q).could you answer why execution is terminating, actually as per console log it should not terminate right????

javin paul said...

Hello @Unknown, the reason is that you cannot control which thread will start first and will continue execution until you call wait() or notify(). It's up to Thread scheduler in Java to choose a thread to start, it can sometime Start Thread A first, finish it execution and the start B, in that case you see the expected result. But if it starts Thread B first and Thread A later then Thread A will call wait() and stuck their forever because notify() has already been called and there is no more thread to call notify, hence you see the execution not terminating.

Also, wait() should be conditional, you must also check the condition once you wake up e.g. wait if buffer is full.

Anonymous said...

private final Queue sharedQ;

making this field final is not letting the constructor initialize the object

public Producer(Queue sharedQ) {
super("Producer");
this.sharedQ = sharedQ;
}

Post a Comment