Concurrency Patterns – Monitor Object
The pattern revolves around synchronization. In short, concurrent threads (clients) can only use the object via a set of synchronized methods. Only one method can run at a time. Typically a synchronized method watches for a certain condition. However, there is no polling involved. Instead, the methods are being notified. That’s an important difference in comparison to the Active Object.
Monitor Object is similar to the Active Object in a sense that it exposes a defined interface via object’s synchronized methods. On the other hand, the methods execute in the client’s thread as there is no notion of a centralised thread control. There is no significant performance overhead either, Since inefficient busy-waiting loops (polling) are replaced with notifications.
Key Components
- 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
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; .. }
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.
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 .. }
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