Home/Learn/Operating Systems/Monitors & Condition Variables

Monitors & Condition Variables

Intermediate
Synchronization

A monitor combines a mutex and condition variables into a single high-level construct; threads call wait() to release the lock and sleep, and notify() to wake sleeping threads.

Overview

A monitor is a higher-level synchronization abstraction that encapsulates shared data, the mutex protecting it, and condition variables for coordination. Every Java object is implicitly a monitor: the synchronized keyword acquires the object's intrinsic lock (mutex), and Object.wait()/notify()/notifyAll() are the condition variable operations. wait() atomically releases the lock and suspends the thread on a wait set. notify() moves one thread from the wait set back to the lock queue. notifyAll() moves all. Because spurious wakeups can occur (a thread may be woken without notify() being called), the correct pattern is always to check the wait condition in a while loop, never an if. Java 5+ ReentrantLock with Condition objects provides explicit, named conditions — more flexible than the intrinsic monitor for complex synchronization patterns.

Java Synchronized + wait/notifyAll: Correct Monitor Pattern

The monitor pattern in Java: synchronized method or block acquires the intrinsic lock. wait() releases the lock and parks the thread. notify()/notifyAll() wakes waiting threads. The woken thread must re-acquire the lock before returning from wait(). The while-loop pattern is mandatory: spurious wakeups make if-based checks unsafe.

Java — monitor-based bounded buffer with while-loop wait pattern
// Correct monitor pattern: while loop around wait()
class BoundedBuffer<T> {
    private final Queue<T>  buffer   = new LinkedList<>();
    private final int       capacity;

    BoundedBuffer(int capacity) { this.capacity = capacity; }

    // Producer: put item, wait if buffer is full
    public synchronized void put(T item) throws InterruptedException {
        while (buffer.size() == capacity) {  // MUST be while, not if!
            wait();  // releases lock, parks thread — avoids CPU spinning
        }
        buffer.add(item);
        notifyAll();  // wake all waiting consumers (and other producers)
        System.out.println("Produced: " + item + "  buffer=" + buffer.size());
    }

    // Consumer: take item, wait if buffer is empty
    public synchronized T take() throws InterruptedException {
        while (buffer.isEmpty()) {  // MUST be while — spurious wakeup protection
            wait();
        }
        T item = buffer.poll();
        notifyAll();  // wake all waiting producers
        System.out.println("Consumed: " + item + "  buffer=" + buffer.size());
        return item;
    }
}

BoundedBuffer<Integer> buf = new BoundedBuffer<>(3);
Thread producer = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try { buf.put(i); } catch (InterruptedException e) { break; }
    }
});
Thread consumer = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try { buf.take(); } catch (InterruptedException e) { break; }
    }
});
producer.start(); consumer.start();
producer.join();  consumer.join();

ReentrantLock + Condition: Named Conditions

The intrinsic monitor has a single wait set — notifyAll() wakes all threads even if most conditions are not satisfied. ReentrantLock with multiple Condition objects fixes this: notFull.signal() wakes only producers; notEmpty.signal() wakes only consumers. This eliminates spurious wakeups between producers and consumers and improves throughput.

Java — ReentrantLock with named Conditions; signal() vs notifyAll() for targeted wake
// ReentrantLock with two named Condition variables — eliminates cross-wakeups
class OptimalBuffer<T> {
    private final Queue<T>         queue    = new ArrayDeque<>();
    private final int              capacity;
    private final ReentrantLock    lock     = new ReentrantLock();
    private final Condition        notFull  = lock.newCondition(); // producers wait here
    private final Condition        notEmpty = lock.newCondition(); // consumers wait here

    OptimalBuffer(int capacity) { this.capacity = capacity; }

    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) notFull.await();  // only producers wait here
            queue.add(item);
            notEmpty.signal();  // wake exactly ONE consumer — no thundering herd
        } finally { lock.unlock(); }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) notEmpty.await();  // only consumers wait here
            T item = queue.poll();
            notFull.signal();  // wake exactly ONE producer
            return item;
        } finally { lock.unlock(); }
    }
}

// Spurious wakeup: JVM may wake a thread from wait() without notify() being called
// This is allowed by the Java Memory Model specification
// ALWAYS use while loop — never if — around wait() calls
// while (condition not met) { lock.wait(); }  ← CORRECT
// if   (condition not met) { lock.wait(); }  ← BUG: proceeds without condition being true

Key Points to Remember

  • 1Monitor = mutex + condition variable + shared data combined in one construct.
  • 2Java object intrinsic lock: synchronized acquires it; wait() releases it and parks; notify()/notifyAll() wakes.
  • 3Spurious wakeups are permitted by the Java spec — always use while loop, never if, around wait().
  • 4notifyAll() wakes all threads from the wait set; notify() wakes one (nondeterministically).
  • 5ReentrantLock + Condition allows multiple named wait sets — avoids waking unrelated threads.
  • 6Always acquire lock in try and release in finally to prevent deadlock on exception.

Interview Questions

Sign in to ask Aria
1

What is a spurious wakeup and why must wait() always be called in a while loop?

MediumGoogle
2

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

EasyAmazon
3

How do ReentrantLock Condition objects improve on the intrinsic monitor?

MediumAtlassian
4

Implement a thread-safe bounded buffer using synchronized + wait/notifyAll.

MediumMicrosoft

Ask Aria about Monitors & Condition Variables

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…