Skip to main content

Monitors and Higher-Level Synchronization

What This Concept Is

A monitor is a programming-language construct that packages together:

  • shared state
  • a mutex that protects every operation on that state
  • one or more condition variables for waiting on predicates over that state

Each operation on the monitor is mutually exclusive with every other operation. The programmer writes only the operations; the language guarantees the mutual exclusion.

Two signaling disciplines exist:

  • Mesa semantics (Java, C#, most modern languages): the signaled thread moves to the ready queue and must re-contend for the monitor. Therefore the predicate must be re-checked in a loop.
  • Hoare semantics (classical, rarely implemented literally): the signaler immediately yields the monitor to a single waiter, so the waiter can assume the predicate holds on wake.

In practice, every language you will use has Mesa semantics.

Why It Matters Here

Monitors are the idiom that Java's synchronized, C#'s lock, and Python's with lock: are based on. Understanding the monitor concept lets you:

  • read synchronized / wait / notify / notifyAll and know what each promises
  • see that a Java ReentrantLock + Condition is a monitor unrolled by hand
  • recognize that the while (!pred) cv.wait(); idiom is the same in every language because they all use Mesa semantics

Monitors are a "supporting" concept in this module because mutexes, condition variables, and semaphores from prior concepts cover everything a monitor does. Monitors add language integration; they do not add expressive power.

Concrete Example

A bounded buffer as a Java monitor:

class BoundedBuffer<T> {
private final Object[] buf = new Object[N];
private int head, tail, count;

public synchronized void put(T x) throws InterruptedException {
while (count == N) wait(); // relinquishes the monitor
buf[tail] = x; tail = (tail + 1) % N; count++;
notifyAll();
}

@SuppressWarnings("unchecked")
public synchronized T get() throws InterruptedException {
while (count == 0) wait();
T x = (T) buf[head]; head = (head + 1) % N; count--;
notifyAll();
return x;
}
}

Compare to the pthread version in concept 07. Structurally identical. Only the syntax moves.

A Python equivalent, with the same shape using threading.Condition:

class BoundedBuffer:
def __init__(self, n):
self.buf = [None]*n; self.head=0; self.tail=0; self.count=0
self.cv = threading.Condition()
def put(self, x):
with self.cv:
while self.count == len(self.buf): self.cv.wait()
self.buf[self.tail] = x
self.tail = (self.tail + 1) % len(self.buf); self.count += 1
self.cv.notify_all()

Common Confusion / Misconception

"synchronized in Java makes my whole class thread-safe." No. It makes each synchronized method mutually exclusive with other synchronized methods on the same lock object. Access through non-synchronized methods, direct field access, or a different lock is unprotected. Composite operations that cross methods still require explicit coordination.

"Hoare-style signaling is what I read about in textbooks, so I can skip the while loop." Not in any production language. All of them use Mesa semantics. while is mandatory.

"Monitors and ReentrantLock are different things." They are the same idea at different abstraction levels. synchronized gives you one implicit lock and one implicit condition queue per object; ReentrantLock + Condition gives you explicit named locks and multiple named conditions per lock, which is useful when you need separate predicates (like not_full and not_empty).

How To Use It

When the language you are writing in offers monitors natively:

  1. Design the concurrent object around a single coarse monitor.
  2. Inside monitor methods, use condition variables for predicate waits; always while.
  3. Break out of the monitor when you discover real contention and need finer locking.
  4. For multiple predicates, use explicit condition objects (for example, Java's newCondition()), not the implicit one.

Prefer the language's native monitor when you can; prefer explicit mutex + condvar when you need control the monitor does not give you.

Check Yourself

  1. What does Mesa signaling semantics force you to do in the waiter?
  2. Why is synchronized not enough to make arbitrary compound operations safe?
  3. When would you prefer ReentrantLock + multiple Conditions over synchronized?

Mini Drill or Application

Port the pthread bounded buffer from concept 07 to Java using synchronized + wait / notifyAll, then to Java using ReentrantLock + two Conditions. Describe the trade-off in one sentence.

Read This Only If Stuck