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/notifyAlland know what each promises - see that a Java
ReentrantLock+Conditionis 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:
- Design the concurrent object around a single coarse monitor.
- Inside monitor methods, use condition variables for predicate waits; always
while. - Break out of the monitor when you discover real contention and need finer locking.
- 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
- What does Mesa signaling semantics force you to do in the waiter?
- Why is
synchronizednot enough to make arbitrary compound operations safe? - When would you prefer
ReentrantLock+ multipleConditions oversynchronized?
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.