Inter-Thread Communication in Java: A Deep Dive into wait(), notify(), and notifyAll()

Satyendra Jaiswal
3 min readDec 1, 2023

--

In this article, we’ll explore one of the fundamental aspects of inter-thread communication in Java — the use of wait(), notify(), and notifyAll().

Understanding the Basics of Inter-Thread Communication

When multiple threads operate on shared resources, it’s crucial to synchronize their activities to prevent data corruption or inconsistent states. Java provides a mechanism for threads to communicate and coordinate through the Object class methods wait(), notify(), and notifyAll().

The wait() Method

The wait() method causes the current thread to release the lock on the object and enter a waiting state. This is often used in conjunction with a loop that checks for a specific condition before allowing the thread to proceed.

Let’s consider a real-world scenario where we have a shared buffer, and a producer thread adds items to it while a consumer thread consumes them:

public class SharedBuffer {
private List<Integer> buffer = new ArrayList<>();
private static final int MAX_SIZE = 5;

public synchronized void produce() throws InterruptedException {
while (buffer.size() == MAX_SIZE) {
wait(); // Buffer is full, wait for the consumer to consume
}

// Produce an item and notify waiting consumers
buffer.add(1);
System.out.println("Produced. Buffer size: " + buffer.size());
notifyAll();
}

public synchronized void consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // Buffer is empty, wait for the producer to produce
}

// Consume an item and notify waiting producers
buffer.remove(0);
System.out.println("Consumed. Buffer size: " + buffer.size());
notifyAll();
}
}

In this example, the produce() and consume() methods are synchronized, and they use wait() to handle situations where the buffer is full or empty, respectively.

The notify() and notifyAll() Methods

The notify() method wakes up one of the threads that are currently waiting on the object. The choice of which thread to wake is not guaranteed and depends on the thread scheduler.

public class SharedBuffer {
// ... (previous code)

public synchronized void produce() throws InterruptedException {
// ... (previous code)
notify(); // Notify one waiting thread (either producer or consumer)
}

public synchronized void consume() throws InterruptedException {
// ... (previous code)
notify(); // Notify one waiting thread (either producer or consumer)
}
}

On the other hand, the notifyAll() method wakes up all the threads that are currently waiting on the object. This is often preferred to ensure that no threads are left waiting indefinitely.

public class SharedBuffer {
// ... (previous code)

public synchronized void produce() throws InterruptedException {
// ... (previous code)
notifyAll(); // Notify all waiting threads (both producers and consumers)
}

public synchronized void consume() throws InterruptedException {
// ... (previous code)
notifyAll(); // Notify all waiting threads (both producers and consumers)
}
}

Example: Coordinating Producer and Consumer Threads

Now, let’s create a simple application that demonstrates the coordination between producer and consumer threads using wait(), notify(), and notifyAll().

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

Thread producerThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
sharedBuffer.produce();
Thread.sleep(100); // Simulate some processing time
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread consumerThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
sharedBuffer.consume();
Thread.sleep(150); // Simulate some processing time
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

producerThread.start();
consumerThread.start();

try {
producerThread.join();
consumerThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

In this example, the Main class creates a SharedBuffer instance and starts a producer and a consumer thread. The producer produces items, and the consumer consumes them, and the coordination between the two is handled by the wait(), notify(), and notifyAll() methods.

Conclusion

In conclusion, mastering inter-thread communication is essential for building robust and efficient multithreaded applications in Java. The wait(), notify(), and notifyAll() methods provide a powerful mechanism for threads to communicate and coordinate their activities. Understanding their usage and incorporating them into your code will help you write thread-safe, efficient, and scalable applications.

--

--