Popular Searches
Popular Course Categories
Popular Courses

Using wait(), notify(), and notifyAll()

Using wait(), notify(), and notifyAll()

Multithreading and Concurrency Fundamentals

Using wait(), notify(), and notifyAll() in Java

Course: Core Java Training

Topic: Inter-Thread Communication Using wait(), notify(), and notifyAll()

Course Link: Core Java Training Course

Demo Link: Register for a Course Demo

Website: JustAcademy


1. Introduction to Inter-Thread Communication

Java supports multithreading, which allows multiple threads to execute concurrently within the same application. In many situations, threads need to coordinate their activities. One thread may need to wait until another thread produces data, completes an operation, or changes a shared condition.

Java provides three important methods for basic inter-thread communication: wait(), notify(), and notifyAll(). These methods are declared in the Object class, so Java objects can be used as monitors for coordinating threads.

For example, in a producer-consumer application, a consumer should wait when no data is available. When the producer adds data, it can notify waiting consumers that the shared state may have changed.

Learning Objectives

  • Understand inter-thread communication and synchronization.
  • Learn the purpose of wait(), notify(), and notifyAll().
  • Understand object monitors and synchronized blocks.
  • Use these methods safely in Java programs.
  • Implement producer-consumer communication.
  • Identify common errors such as missed notifications and deadlocks.

2. What Is Inter-Thread Communication?

Inter-thread communication is a mechanism through which threads coordinate their actions based on shared data or conditions. Rather than repeatedly checking a condition in a loop, a thread can wait until another thread signals that a change may have occurred.

Consider a simple example: a consumer needs to process a message, but the producer has not created one yet. The consumer can wait until the producer supplies the message and signals the waiting thread.

Why Is Inter-Thread Communication Needed?

  • To coordinate tasks that depend on one another.
  • To prevent a consumer from processing unavailable data.
  • To reduce unnecessary repeated checking of shared conditions.
  • To coordinate access to shared resources.
  • To build producer-consumer systems and other concurrent workflows.

3. Understanding the Object Monitor

Every Java object can act as a monitor. A monitor provides a synchronization mechanism that allows only one thread at a time to own that object's intrinsic lock.

A thread can own an object's monitor by entering a synchronized instance method, a synchronized block using that object, or a static synchronized method when the monitor is the class object.

The wait(), notify(), and notifyAll() methods must be called by a thread that currently owns the monitor of the object on which the method is invoked.

synchronized (lock) {
    // The current thread owns lock's monitor here.
    lock.wait();
}

In this example, the current thread owns the monitor for lock when it calls wait(). The call releases that monitor and places the thread in the object's wait set. When the thread is awakened, it must reacquire the monitor before continuing.

4. Understanding wait()

The wait() method causes the current thread to wait on an object's monitor. The thread releases that object's monitor while waiting, allowing another thread to enter a synchronized section using the same monitor.

Syntax

synchronized (lock) {
    lock.wait();
}

The wait() method is declared in the Object class and throws InterruptedException.

Forms of wait()

Method Description
wait() Waits until notification, interruption, or a possible spurious wakeup.
wait(long timeout) Waits until notification, interruption, a possible spurious wakeup, or timeout expiration.
wait(long timeout, int nanos) Waits with a timeout expressed in milliseconds plus additional nanoseconds.

A timeout value of zero means to wait indefinitely unless the thread is interrupted or awakened. A negative timeout is invalid.

Important Characteristics of wait()

  • It must be called while owning the target object's monitor.
  • It releases the monitor of the object on which it is called.
  • It does not release monitors for other objects that the thread may hold.
  • After being awakened, the thread must reacquire the same monitor before returning from the call.
  • It can throw InterruptedException.
  • A thread can wake up without a notification, so the condition must be checked again.

5. Understanding notify()

The notify() method wakes one thread waiting on the same object's monitor. If multiple threads are waiting, Java does not guarantee which one will be selected.

Syntax

synchronized (lock) {
    lock.notify();
}

Calling notify() does not immediately transfer the monitor lock to the selected thread. The notifying thread continues until it leaves the synchronized section or otherwise releases the monitor. The awakened thread must then compete to reacquire the monitor.

Example

class Message {
    synchronized void waitForMessage() {
        try {
            System.out.println("Waiting for a message...");
            wait();
            System.out.println("Notification received");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Waiting was interrupted");
        }
    }

    synchronized void sendMessage() {
        System.out.println("Sending notification");
        notify();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Message message = new Message();

        Thread receiver = new Thread(message::waitForMessage);
        receiver.start();

        Thread.sleep(500);

        synchronized (message) {
            message.notify();
        }
    }
}

Explanation: The receiver calls wait() while holding the message monitor. The main thread later acquires the same monitor and calls notify(). The receiver can continue only after it reacquires that monitor.

Important limitation: This example demonstrates the methods, but it does not use a shared condition to protect against every timing scenario. In production code, check a condition inside a loop, as shown in later examples.

6. Understanding notifyAll()

The notifyAll() method wakes all threads waiting on the same object's monitor. Those threads do not execute simultaneously inside the synchronized section. Each must compete to reacquire the monitor, and each should check whether its required condition is now true.

Syntax

synchronized (lock) {
    lock.notifyAll();
}

Example

class SharedSignal {
    private boolean ready = false;

    synchronized void awaitSignal() {
        while (!ready) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }

        System.out.println(
            Thread.currentThread().getName() + " received the signal"
        );
    }

    synchronized void signalAll() {
        ready = true;
        notifyAll();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        SharedSignal signal = new SharedSignal();

        Thread t1 = new Thread(signal::awaitSignal, "Worker-1");
        Thread t2 = new Thread(signal::awaitSignal, "Worker-2");
        Thread t3 = new Thread(signal::awaitSignal, "Worker-3");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(500);
        signal.signalAll();
    }
}

Possible output:

Worker-1 received the signal
Worker-3 received the signal
Worker-2 received the signal

The output order may vary. The example sets the shared condition to true before calling notifyAll(). All waiting threads can then proceed after reacquiring the monitor.

7. Difference Between wait(), notify(), and notifyAll()

Feature wait() notify() notifyAll()
Purpose Causes the current thread to wait. Wakes one waiting thread. Wakes all waiting threads.
Monitor ownership required Yes Yes Yes
Releases the target monitor Yes, while waiting No No
Declares InterruptedException Yes No No
Which thread continues? The waiting thread may resume after being awakened and reacquiring the monitor. One selected waiting thread may resume. All waiting threads may resume one at a time after reacquiring the monitor.
Typical use Wait until a condition becomes true. Signal a change when one waiter can proceed. Signal a change to all waiters, especially when conditions differ.

8. Why Must These Methods Be Used Inside synchronized?

The monitor provides the lock and coordination mechanism that makes these operations meaningful. Calling wait(), notify(), or notifyAll() without owning the target object's monitor causes an IllegalMonitorStateException.

Incorrect Example

public class Main {
    public static void main(String[] args) {
        Object lock = new Object();

        lock.notify(); // Incorrect: monitor is not owned
    }
}

This code throws IllegalMonitorStateException because the main thread does not own the monitor for lock.

Correct Example

public class Main {
    public static void main(String[] args) {
        Object lock = new Object();

        synchronized (lock) {
            lock.notify();
        }
    }
}

This call is valid. If no thread is waiting on the monitor, however, the notification does not get stored for a future waiter. A later call to wait() will not automatically receive that earlier notification.

9. Why Should wait() Be Used Inside a while Loop?

A thread should recheck the condition after waking up. A notification only indicates that something may have changed; it does not guarantee that the condition the thread needs is true.

There are several reasons for this rule:

  • A thread can experience a spurious wakeup.
  • Another thread may change the condition before the awakened thread reacquires the monitor.
  • With notifyAll(), several threads may wake, but only some may be able to proceed.
  • The thread must verify the shared state before continuing.

Recommended Pattern

synchronized (lock) {
    while (!condition) {
        lock.wait();
    }

    // Continue only when condition is true.
}

Avoid using if for this purpose. A while loop checks the condition again whenever the thread resumes.

10. Producer-Consumer Problem

The producer-consumer problem is a classic example of inter-thread communication. A producer generates data, while a consumer processes that data. If the shared storage is empty, the consumer must wait. If the storage is full, the producer may need to wait.

Basic Workflow

  1. The consumer checks whether data is available.
  2. If no data is available, the consumer calls wait().
  3. The producer adds data to the shared storage.
  4. The producer calls notifyAll() to signal that the shared state may have changed.
  5. The consumer wakes up, reacquires the monitor, and checks the condition again.
  6. The consumer processes the available data.

Complete Producer-Consumer Example

class SharedBuffer {
    private String message;
    private boolean available = false;

    public synchronized void produce(String value)
            throws InterruptedException {
        while (available) {
            wait();
        }

        message = value;
        available = true;

        System.out.println("Produced: " + value);
        notifyAll();
    }

    public synchronized String consume()
            throws InterruptedException {
        while (!available) {
            wait();
        }

        String value = message;
        available = false;

        System.out.println("Consumed: " + value);
        notifyAll();

        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer();

        Thread producer = new Thread(() -> {
            try {
                buffer.produce("Java Message");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer");

        Thread consumer = new Thread(() -> {
            try {
                buffer.consume();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Consumer");

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

Possible output:

Produced: Java Message
Consumed: Java Message

The example uses a single-slot buffer. The available flag describes whether the slot contains a message. Both methods use synchronized access and condition-checking loops to coordinate the threads safely.

Note: For real applications, higher-level utilities such as BlockingQueue are often simpler and safer than implementing a buffer with low-level monitor methods.

11. wait() Versus sleep()

Both wait() and sleep() can pause a thread, but they have different purposes and lock behavior.

Feature wait() sleep()
Declared in Object Thread
Purpose Thread coordination based on a monitor. Pause execution for a period.
Monitor requirement Must own the target object's monitor. No monitor ownership required.
Releases target monitor Yes No
Typical end condition Notification, interruption, timeout, or spurious wakeup. Elapsed sleep time or interruption.
Exception InterruptedException InterruptedException

12. Common Errors and Troubleshooting

Error or Problem Cause Solution
IllegalMonitorStateException Calling wait or notification methods without owning the target monitor. Call the method within a synchronized block or method using the same object.
Thread waits indefinitely No relevant notification occurs, or the condition never becomes true. Check the condition and ensure all state-changing paths signal appropriately.
Missed notification Notification occurs before a thread starts waiting. Store the condition in shared state and check it in a loop while holding the monitor.
Unexpected continuation The thread assumes waking up means the condition is true. Use a while loop to recheck the condition.
Deadlock Threads wait for conditions that can never be satisfied or hold other required locks. Review lock ordering and state transitions; avoid holding unrelated locks while waiting.
InterruptedException ignored The program discards an interruption request. Handle cancellation or restore the interrupt status when appropriate.

13. Best Practices

  • Call wait(), notify(), and notifyAll() only while owning the target object's monitor.
  • Protect the condition and its related shared data with the same monitor.
  • Always check wait conditions in a while loop.
  • Update the shared state before issuing a notification.
  • Prefer notifyAll() when multiple kinds of waiters may be waiting on the same monitor and the correct waiter is not known.
  • Use notify() only when the design ensures that waking one arbitrary waiter is sufficient.
  • Do not assume a notified thread immediately starts running.
  • Handle interruption explicitly and avoid silently swallowing InterruptedException.
  • Keep synchronized sections short and avoid unnecessary nested locking.
  • Consider BlockingQueue, CountDownLatch, Semaphore, or other concurrency utilities for complex coordination.

14. Practice Exercises

  1. Create a program in which one thread waits for a shared Boolean condition to become true.
  2. Write a program that calls notify() to wake one waiting thread.
  3. Modify the program to use notifyAll() with three waiting threads.
  4. Create a producer-consumer example using a single shared message.
  5. Demonstrate what happens when notification occurs before a thread calls wait().
  6. Modify a waiting thread to handle InterruptedException correctly.
  7. Explain why while is safer than if around a call to wait().

15. Mini Project: Message Notification System

Objective: Create a small system in which one thread waits for a message and another thread publishes it.

Requirements

  • Create a shared class containing a message and a Boolean state flag.
  • Use a synchronized method for waiting until a message is available.
  • Use wait() inside a while loop.
  • Publish the message by updating the shared state.
  • Use notifyAll() to wake waiting threads.
  • Handle interruptions without ignoring cancellation requests.

Expected Learning Outcome

After completing this project, learners should understand how shared state, monitor ownership, waiting threads, and notifications work together in a simple concurrent application.

16. Interview Questions and Answers

Q1. What are wait(), notify(), and notifyAll() in Java?

They are methods declared in the Object class and are used for communication between threads waiting on the same object's monitor.

Q2. Why are these methods declared in Object rather than Thread?

Monitor locks belong to objects. These methods coordinate threads through an object's monitor, so they are defined in Object.

Q3. Why must wait() be called inside synchronized code?

The calling thread must own the target object's monitor so it can safely enter that object's wait set and release the monitor while waiting.

Q4. What is the difference between notify() and notifyAll()?

notify() wakes one arbitrarily selected waiting thread, while notifyAll() wakes all threads waiting on the same monitor.

Q5. Does notify() release the monitor lock?

No. The notifying thread retains the monitor until it leaves the synchronized region or otherwise releases it.

Q6. Does wait() release the monitor?

Yes. It releases the monitor of the object on which it is called while the thread waits. Other locks held by that thread are not automatically released.

Q7. Why should wait() be called inside a while loop?

Because a thread can wake up spuriously or another thread may change the condition before the awakened thread reacquires the monitor. The loop ensures the condition is checked again.

Q8. What happens if wait() is called without owning the monitor?

Java throws IllegalMonitorStateException.

Q9. Can notify() be called when no threads are waiting?

Yes, if the caller owns the monitor. The call does not store a notification for a future thread.

Q10. What is the producer-consumer problem?

It is a synchronization problem in which a producer creates data and a consumer processes it, requiring coordination when data is unavailable or storage is full.

Q11. What is the difference between wait() and sleep()?

wait() is monitor-based coordination and releases the target monitor while waiting. sleep() pauses a thread without releasing any monitor locks it holds.

Q12. What exception can wait() throw?

wait() can throw InterruptedException if the waiting thread is interrupted, and IllegalMonitorStateException if the current thread does not own the monitor.

17. Quick Revision Checklist

  • wait() makes the current thread wait on an object's monitor.
  • notify() wakes one waiting thread on that monitor.
  • notifyAll() wakes all threads waiting on that monitor.
  • All three methods require ownership of the target object's monitor.
  • wait() releases the target monitor while waiting.
  • notify() and notifyAll() do not immediately release the monitor.
  • A notified thread must reacquire the monitor before continuing.
  • Use a while loop to check the waiting condition.
  • Update shared state before notifying other threads.
  • Handle InterruptedException appropriately.
  • Use higher-level concurrency utilities when they simplify the design.

18. Conclusion

The wait(), notify(), and notifyAll() methods are fundamental tools for inter-thread communication in Java. They help threads coordinate their actions through a shared object's monitor, particularly when one thread must wait for a condition to become true.

Correct usage requires synchronized access, condition-checking loops, careful handling of interruption, and a clear understanding of monitor ownership. For many production applications, Java's higher-level concurrency utilities can provide simpler and more maintainable solutions.


19. Additional Learning Resources

Previous Next
whatsapp