← Back to Multithreading Course

PART 4 · CLASS 19

Communication with wait(), notify() and notifyAll()

Sometimes threads need to communicate with each other. One thread may need to wait until another thread completes some work. Let's understand how Java helps threads communicate and coordinate with one another.

1. Why Do Threads Need to Communicate?

Imagine a simple situation.

You are preparing tea. You put water on the stove and wait for it to boil. You don't want to add tea leaves before the water is ready.

Similarly, in a multithreaded application, one thread may need to wait until another thread has completed a particular task.

For example:

  • One thread produces data, and another thread consumes it.
  • One thread prepares a resource, and another thread needs it.
  • One thread processes a task, and another thread waits for the result.

Java provides three important methods for basic thread communication:

  • wait()
  • notify()
  • notifyAll()

2. Understanding wait()

The wait() method tells the current thread to wait until another thread notifies it.

When a thread calls wait() on an object, it releases that object's monitor lock and enters the waiting state.

It remains waiting until it receives a notification, is interrupted, or otherwise wakes up.

An important point: wait() must be called while the current thread owns the object's monitor lock. In practice, this means calling it inside a synchronized method or synchronized block using that same object.

If we call wait() without owning that lock, Java throws an IllegalMonitorStateException.

3. Understanding notify()

The notify() method wakes up one thread that is waiting on the same object's monitor.

Imagine Thread A is waiting on an object. Thread B performs some work and calls notify() on that same object. Thread A becomes eligible to wake up.

However, there is one important detail.

The notification does not immediately give Thread A the lock. Thread B must first release the lock, and then Thread A can compete to acquire it.

Also, notify() wakes only one waiting thread. If multiple threads are waiting, Java does not guarantee which one will be selected.

4. Understanding notifyAll()

What if multiple threads are waiting on the same object?

Instead of waking just one thread, we can use notifyAll().

It wakes up all threads waiting on that object's monitor. They then compete to acquire the object's lock.

Remember, waking up does not mean all threads execute simultaneously inside the synchronized section. Only one thread at a time can own the same object's monitor.

A simple way to remember the difference:

  • notify() wakes one waiting thread.
  • notifyAll() wakes all waiting threads.

5. Let's Understand Them with an Example

Let's create a simple example where one thread waits for a message, and another thread provides that message.

class Message {

    private String message;

    public synchronized void waitForMessage()
            throws InterruptedException {

        while (message == null) {
            System.out.println("Waiting for message...");
            wait();
        }

        System.out.println("Received: " + message);
    }

    public synchronized void sendMessage(String newMessage) {

        message = newMessage;

        System.out.println("Message sent!");

        notify();
    }
}

public class Main {

    public static void main(String[] args) {

        Message message = new Message();

        Thread receiver = new Thread(() -> {
            try {
                message.waitForMessage();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread sender = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }

            message.sendMessage("Hello from another thread!");
        });

        receiver.start();
        sender.start();
    }
}

Let's understand the example step by step.

  1. We create a shared Message object.
  2. The receiver thread calls waitForMessage().
  3. Since the message is null, it enters the while loop.
  4. It calls wait() and releases the object's lock.
  5. The sender thread waits for two seconds and then sends a message.
  6. It calls notify() on the same Message object.
  7. The receiver wakes up and waits to reacquire the object's lock.
  8. Once it gets the lock, it checks the message again and prints it.

The output will look similar to this:

Waiting for message...
Message sent!
Received: Hello from another thread!

The exact timing of the output can vary, but the receiver will print the message after it has been set and the receiver has resumed.

6. Why Do We Use while Instead of if?

In our example, notice that we used a while loop instead of an if statement.

Why?

Because a waiting thread can sometimes wake up even without receiving a notification. This is called a spurious wakeup.

Also, another thread may change the shared condition before the awakened thread gets the lock.

Therefore, after waking up, the thread should always check whether the condition it was waiting for is actually true.

That's why we write:

while (message == null) {
    wait();
}

The thread keeps checking the condition. If the message is still null, it waits again.

This is an important practice whenever we use wait().

7. Important Rules to Remember

  • wait(), notify() and notifyAll() are methods of the Object class.
  • They must be called while holding the monitor lock of the same object.
  • Calling wait() releases that object's monitor lock.
  • Calling notify() or notifyAll() does not immediately release the lock.
  • The waiting thread must reacquire the lock before continuing.
  • Always check the waiting condition in a while loop.

One more thing: these methods work with the monitor of the object on which they are called. If one thread waits on one object and another thread notifies a different object, they are not communicating through that wait set.

Test Your Understanding

Let's see how well you understood this class!

Question 1: What is the purpose of wait()?

Question 2: Where must we call wait()?

Question 3: What does notify() do?

Question 4: What is the difference between notify() and notifyAll()?

Question 5: Why should we use a while loop with wait()?

Class Summary

Let's quickly revise what we learned today:

  • wait() makes a thread wait and releases the object's monitor lock.
  • notify() wakes one thread waiting on the same object.
  • notifyAll() wakes all threads waiting on the same object.
  • The awakened thread must reacquire the lock before continuing.
  • These methods must be called while holding the same object's monitor lock.
  • Use a while loop to check the waiting condition again after waking up.

The key idea is simple: threads can wait for a condition and notify one another when that condition may have changed.

NEXT CLASS · CLASS 20

sleep() vs. wait()

Both sleep() and wait() can make a thread pause its execution. But they work differently, especially when it comes to releasing locks.

In our next class, we will understand the difference between these two important methods with simple examples.

0% Completed

You have completed 19 out of 46 classes.