Java implementation of Monitor Concurrency Pattern
Monitor
is a synchronization construct that allows threads to have both
thread-safe access to underlying monitored object or value (hereinafter
monitored entity) and wait for it to mutate (if it is a mutable object)
or change (if it is an immutable value) to a desirable state.
Monitor<Entity>
class allows threads to access monitored entity in either
exclusive (write) or shared (read) mode. Entity
here refers to type
of monitored entity.
For accessing monitored entity Monitor
class has several overloaded
read
and write
methods.
-
all
read
methods takeConsumer<Entity>
as a first argument. It could be a lambda function that reads monitored entity but must not mutate it. The lambda function takes monitored entity as its only argument and must return no value. -
all
write
methods takeFunction<Entity, Entity>
as a first argument. It could be a lambda function that accesses monitored value exclusively. The lambda function must return a reference to the monitored entity. This could be the same value that received by the lambda function in its argument (if the lambda function mutates monitored entity) or a new value (if the lambda function's intent is to change the reference to monitored entity). -
read
andwrite
methods might takePredicate<Entity>
as a second argument. It could be a lambda function that evaluates monitored entity and indicates if the desirable state is achieved. Concurrent predicates examine monitored entity exclusively. -
read
andwrite
methods might take a maximum time to wait as a third argument. If the time detectably elapsed the methods return false and monitored entity is not accessed.
Unfortunately, Java type system cannot guarantee through Monitor
's contract
that consumers and predicates do not mutate monitored entity. Monitor
's
behavior in this case is undefined.
Consumers and predicates must not mutate monitored entity even if it is an
instance of Atomic
classes (see java.util.concurrent.atomic
) or synchronized
in some other way. Monitor
is not able to detect changes in this case and will
not re-evaluate predicates of other waiting threads.
Sandbox
class allows threads to create a local copy of monitored entity and
then Compare and Swap the mutated or changed entity back to monitor. The
Entity
must be Cloneable
in order to create its local copy.
Sandbox
instance is not reentrant. Each thread must create a local instance
of Sandbox
.
For more information see Project website or JavaDoc.
Monitor<Queue<String>> outputQueue =
new Monitor<>(new LinkedList<String>());
// ... monitoring thread
while(true) {
// wait for a new string in queue
outputQueue.writeAccess(
queue -> {
System.out.println(queue.pull());
// Write access lambda must return its argument
// to preserve reference to the queue
return queue;
},
// wake up when the queue is not empty
queue -> !queue.isEmpty()
);
}
// ... somewhere in some other thread
outputQueue.writeAccess(
queue -> {
queue.add("Hello Monitor!");
return queue;
}
);
Monitor
is different from Java synchronized
keyword in several crucial
ways:
-
Unlike
synchronized
keywordMonitor
allows threads to wait for monitored entity to mutate or change to a desirable state. -
Unlike
synchronized
keywordMonitor
allows threads to access monitored entity in either exclusive or shared mode.
Object.wait()
method allow threads to wait for a desirable event to occur.
The event occurrence is determined by actor thread and signaled by invoking
Object.notify()
or Object.notifyAll()
methods.
Unlike Object.wait()
method Monitor
allows threads to wait for desirable
state which is defined by a Predicate
. The actor thread behavior is decoupled
from waiting threads intentions.
-
Even though
Monitor
provides an ability to perform Compare and Swap over monitored entity it cannot be considered lock-free or wait-free construct and thus it is not recommended for use in lock-free or wait-free algorithms. -
Atomic
classes could have less overhead and be more effective when used for short-term non-blocking spin lock or wait constructs. Even thoughMonitor
has an overhead for blocking and resuming threads it could be more effective it case of mid- or long-term blocks and waits as blocked and waiting threads do not consume CPU time.
Java 8 or higher
<dependency>
<groupId>com.github.vfro</groupId>
<artifactId>monitor</artifactId>
<version>4.0.2</version>
</dependency>