Java
wait and notify
multithreading
concurrency
programming tutorial

A simple scenario using wait and notify in java

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In concurrent programming with Java, managing thread synchronization is crucial for developing robust multi-threaded applications. The wait() and notify() methods play a significant role in thread communication, enabling threads to pause execution until specific conditions are met and to wake up waiting threads when those conditions change.

In this article, we'll explore a simple scenario involving these methods and delve into the technical explanations necessary for implementing efficient thread coordination.

Understanding wait() and notify()

Key Methods

  • wait(): This method is called on a monitor object and causes the current thread to wait until another thread invokes notify() or notifyAll() on the same object. The thread releases ownership of the monitor and goes into the waiting state.
  • notify(): This method wakes up one of the threads waiting on the monitor object. The awakened thread will not run immediately but will compete for the monitor's lock.
  • notifyAll(): Similar to notify(), notifyAll() wakes up all threads waiting on the monitor object.

Synchronization Prerequisite

Both wait() and notify() methods must be called while holding the monitor lock, typically achieved through a synchronized block or synchronized method.

java
synchronized (monitor) {
    monitor.wait();
}
java
synchronized (monitor) {
    monitor.notify();
}

Scenario: Simple Producer-Consumer Model

Consider a common problem in concurrency: the Producer-Consumer scenario. In this model, producers add items to a shared buffer, while consumers remove items. Synchronization is necessary to ensure the two parties operate correctly without conflicting.

Implementation

Here's a simple implementation with wait() and notify() to manage the buffer state:

java
1import java.util.LinkedList;
2import java.util.Queue;
3
4class ProducerConsumer {
5    private final Queue<Integer> buffer = new LinkedList<>();
6    private final int maxBufferSize = 5;
7
8    public void produce() throws InterruptedException {
9        int value = 0;
10        while (true) {
11            synchronized (this) {
12                while (buffer.size() == maxBufferSize) {
13                    wait();  // Wait if the buffer is full.
14                }
15                buffer.add(value);
16                System.out.println("Produced: " + value);
17                value++;
18                notify();  // Notify consumers that buffer is not empty.
19            }
20            Thread.sleep(1000); // Simulate time taken to produce items.
21        }
22    }
23
24    public void consume() throws InterruptedException {
25        while (true) {
26            synchronized (this) {
27                while (buffer.isEmpty()) {
28                    wait();  // Wait if the buffer is empty.
29                }
30                int value = buffer.poll();
31                System.out.println("Consumed: " + value);
32                notify();  // Notify producers that space is available.
33            }
34            Thread.sleep(1500); // Simulate time taken to process items.
35        }
36    }
37}
38
39public class Main {
40    public static void main(String[] args) {
41        ProducerConsumer producerConsumer = new ProducerConsumer();
42
43        Thread producerThread = new Thread(() -> {
44            try {
45                producerConsumer.produce();
46            } catch (InterruptedException e) {
47                Thread.currentThread().interrupt();
48                throw new RuntimeException(e);
49            }
50        });
51
52        Thread consumerThread = new Thread(() -> {
53            try {
54                producerConsumer.consume();
55            } catch (InterruptedException e) {
56                Thread.currentThread().interrupt();
57                throw new RuntimeException(e);
58            }
59        });
60
61        producerThread.start();
62        consumerThread.start();
63    }
64}

Explanation

  • Synchronized Blocks: Used to acquire locks on the monitor. Only one thread can execute within a synchronized block.
  • wait(): When the buffer is full or empty, depending on the situation, the producer or consumer thread releases the lock and waits.
  • notify(): Signals the waiting counterpart to check the buffer state, ensuring that data or space is adequately managed.

Important Notes

  • Awareness of Spurious Wakeups: Threads woken up by wait() must recheck the condition due to potential spurious wakeups. This is why wait() is generally used within a loop checking the condition.

Summary Table

ConceptDescription
Monitor ObjectObject on which synchronization methods like wait() and notify() are invoked.
synchronized Block/MethodEnsures mutual exclusion, making the block of statements thread-safe.
wait() MethodReleases the lock and causes the current thread to wait until notified. When resumed, the thread rechecks the condition.
notify() and notifyAll()notify() wakes up a single waiting thread, while notifyAll() wakes up all waiting threads.
Spurious WakeupsThreads can wake up without notify(). Developers should re-check conditions within a loop before proceeding.

Additional Details

Deadlocks

Improper use of wait() and notify() can lead to deadlocks, where threads are stuck waiting for conditions that aren't met. Thoroughly verify synchronizations and ensure consistent lock acquisitions.

Thread Priority

The operating system, not the JVM, typically controls thread scheduling. Thus, the order in which waiting threads are notified isn't guaranteed.

Alternatives

Other Java concurrency utilities, such as BlockingQueue, exist and simplify the producer-consumer scenario by handling synchronization internally.

Conclusion

The use of wait() and notify() in Java is fundamental for managing thread synchronization and coordination. By understanding and implementing these mechanisms, one can develop effective multi-threaded applications where threads interact safely and efficiently.


Course illustration
Course illustration

All Rights Reserved.