- Monitor Object: exposes synchronized methods as the only means of client access
- Synchronized Methods: guarantee thread-safe access to the internal state
- Monitor Lock: used by the synchronized methods to serialise method invocation
- Monitor Condition: caters for cooperative execution scheduling
Advantages
- Simplified synchronization: All of the hard work is offloaded to the object itself, clients are not concerned with synchronization issues.
- Cooperative execution scheduling: Monitor conditions are used to suspend / resume method execution.
- Reduced performance overhead: Notifications over inefficient polling.
Drawbacks
- Synchronization tightly coupled with core logic: Synchronization code blends into the business logic which breaks the principle of separation of concerns.
- Nested monitor lockout problem: An endless wait for a condition to become true can occur when a monitor object is nested into another kind of its own. In Java for example, monitor locks are not shared between two separate classes. Thus, it can happen that the outer monitor is never released and any threads watching that monitor would be kept waiting.
Example Implementation
Inspired by a blog post Java Monitor Pattern I came up with a hypothetical usage of a public toilet. Not only does it reminiscence of what makes us all equal, but it also comprises pattern's dominant attributes. A toilet is either occupied or vacant, hence the locked / unlocked parallel. It also should only be used by a single person at a time (race conditions).
Only a vacant toilet can be entered. Once in, the visitor is granted to leave:
public interface Toilet {
boolean enter();
void quit();
boolean isOccupied();
}
Now, the challenge is to ensure, under any circumstances, that the toilet only be used by a single person. Should that condition fail, the toilet becomes flooded:
public class ToiletFloodedException extends RuntimeException {
}
An obviously ignorant implementation of the Toilet interface ..:
public class FilthyToilet implements Toilet {
// a concurrent visitor counter.
private int counter = 0;
..
}
.. has unavoidable consequences: console output after having run the FilthyToiletMultiThreadedTest.java:
The toilet was flooded 25 times under a moderate load.
The toilet was flooded 38 times under a heavy load.
The toilet was flooded 96 times under an extreme load.
The correct implementation makes use of the Monitor Object pattern:
public class CleanToilet implements Toilet {
// Monitor Lock used by the synchronized methods
private final ReentrantLock lock;
// Monitor Condition - the toilet can only
// be used by a single person at a time
private Condition oneAtATimeCondition;
// The guarded object's state - the 'volatile' flag
// is crucial for work signalling
private volatile int counter;
// all of the public methods are synchronized
..
}
The synchronization is ensured by using a lock along with a condition. The lock holds as long as the condition holds true:
public boolean enter() {
lock.lock();
try {
while (counter > 0) {
// wait while the toilet is being used
oneAtATimeCondition.awaitUninterruptibly();
}
if (++counter == 1) {
// the toilet has been successfully acquired
oneAtATimeCondition.signal();
}
return isOccupied();
} finally {
lock.unlock();
}
}
Source Code
Resources