Fundamentals 8 min read

Why wait() Must Be Used Inside a while Loop Instead of if in Java Synchronization

This article explains why the wait() method in Java synchronized blocks must be placed inside a while loop rather than an if statement, illustrating the issue with concurrent producers and consumers through code examples and detailing when to use notifyAll versus notify.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Why wait() Must Be Used Inside a while Loop Instead of if in Java Synchronization

Most developers write synchronized code using an synchronized (obj) { while (condition) { wait(); } // business logic } pattern, but many wonder why a while loop is required instead of a simple if . The article shows that using if can lead to race conditions when multiple threads wait and are later notified.

Consider a bounded buffer implementation:

static class Buf {
    private final int MAX = 5;
    private final ArrayList
list = new ArrayList<>();
    synchronized void put(int v) throws InterruptedException {
        if (list.size() == MAX) {
            wait();
        }
        list.add(v);
        notifyAll();
    }
    synchronized int get() throws InterruptedException {
        if (list.size() == 0) {
            wait();
        }
        int v = list.remove(0);
        notifyAll();
        return v;
    }
    synchronized int size() { return list.size(); }
}

Running one producer thread and ten consumer threads with this code quickly throws java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 because a consumer may wake up, assume the list is still empty, and attempt to remove an element.

The failure scenario is explained step‑by‑step: two consumer threads wait on an empty list, a producer adds an element and calls notifyAll , both consumers are awakened, the first removes the element, then the second proceeds without re‑checking the condition and crashes.

The fix is to replace the if with a while loop in get() (and similarly in put() when the buffer is full):

synchronized int get() throws InterruptedException {
    while (list.size() == 0) {
        wait();
    }
    int v = list.remove(0);
    notifyAll();
    return v;
}

Additional experiments with varying numbers of producer and consumer threads confirm that the while loop is essential for correct coordination.

The article also discusses when to use notifyAll versus notify . Using notify can cause deadlock because the awakened thread is unpredictable; a consumer may wake another consumer that still finds the buffer empty, leading to a situation where all threads are waiting.

synchronized void put(int v) throws InterruptedException {
    if (list.size() == MAX) {
        wait();
    }
    list.add(v);
    notify();
}

synchronized int get() throws InterruptedException {
    while (list.size() == 0) {
        wait();
    }
    int v = list.remove(0);
    notify();
    return v;
}

Because the JVM cannot guarantee which waiting thread will be notified, notifyAll is generally safer for producer‑consumer scenarios.

Overall, the article demonstrates the importance of re‑checking conditions after a wait, using while loops, and preferring notifyAll to avoid subtle concurrency bugs.

JavaconcurrencySynchronizationmultithreadingwait-notify
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.