Active Object and Monitor Object are both widely used concurrency design patterns. They have some similarities, as well as notable differences. This article presents these two patterns side by side in the hope of explaining them clearly.
Active Object is a concurrency pattern in which we try to separate the invocation of a method from its execution. Typically, an active object provides synchronous methods and executes the method calls in an asynchronous way. An active object usually has its own thread of control.
This pattern is useful in refactoring legacy projects by introducing concurrency capability.
The following class diagram shows the basic structure of this pattern.
The key elements in active object pattern are:
Proxy (or Client Interface) - A public method provided by active object to clients.
Dispatch Queue - A list of pending requests from clients.
Scheduler - Determines the order to execute the requests.
Result Handle (or Callback) - This allows the result to be obtained by proxy after a request is executed.
The following code shows an example of active object. The ActiveObject class uses a separate thread to execute the tasks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
import java.util.concurrent.PriorityBlockingQueue; class ActiveObject { class Task implements Comparable < Task > { // smaller number means higher priority int priority; String name; Task(String name, int priority) { this.name = name; this.priority = priority; } public int compareTo(Task other) { return Integer.compare(this.priority, other.priority); } } private PriorityBlockingQueue < Task > dispatchQueue = new PriorityBlockingQueue < > (); public ActiveObject() { // A priority scheduler new Thread(() - > { while (true) { try { Task task = dispatchQueue.take(); System.out.println("Executing task " + task.name); } catch (InterruptedException e) { break; } } }) .start(); } public void doTask(String name, int priority) { dispatchQueue.put(new Task(name, priority)); } }
In the above example, the dispatchQueue acts as both a dispatch queue and a scheduler. Of course we can separate the scheduler and the dispatch queue. The implementation is up to you.
To test the ActiveObject, we can create several threads calling doTask method at the same time, as shown in the following code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Testing ActiveObject
ActiveObject obj = new ActiveObject();
// Call doTask in different threads
Thread t1 = new Thread(() - > {
obj.doTask("1", 2);
});
Thread t2 = new Thread(() - > {
obj.doTask("2", 0);
});
Thread t3 = new Thread(() - > {
obj.doTask("3", 1);
});
t1.start();
t2.start();
t3.start();
Generally, Monitor Object is a pattern that controls the concurrent access of a method in an object. If there are some concurrent threads calling a method at the same time, only one thread can execute this method.
Compared with Active Object, Monitor Object does not have its own thread of control.
Here are some important concepts in Monitor Object pattern:
Scoped lock
Scoped lock is used to protect a critical section.
Condition variables
A condition variable is used to suspend a thread. Once a condition is satisfied, threads waiting on this condition are awoken.
Java has built-in support of concurrency. Every object in Java has an intrinsic lock, with synchronized keyword we can easily protect a critical section. We can also implement condition variables using wait/notify methods.
The following code is an example of Monitor Object. An instance of MonitorQueue could be shared by several threads. Both append and poll methods are protected by “synchronized” keyword so only one thread can execute them. If there is no element in the queue, threads calling poll method are suspended.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.LinkedList;
public class MonitorQueue {
private LinkedList < Integer > queue = new LinkedList < > ();
public synchronized void append(int num) throws InterruptedException {
queue.addLast(num);
notifyAll();
}
public synchronized int poll() throws InterruptedException {
while (queue.size() == 0) {
wait();
}
return queue.removeFirst();
}
}
From Java 1.5, we are able to use ReentrantLock class to implement Monitor Object pattern instead of the intrinsic lock. The newCondition method of ReentrantLock creates condition variables we need.
Using ReentrantLock, we can rewrite the MonitorQueue class as below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class MonitorQueue {
private LinkedList < Integer > queue = new LinkedList < > ();
final Lock lock = new ReentrantLock();
final Condition notEmpty = lock.newCondition();
public void append(int num) throws InterruptedException {
lock.lock();
try {
queue.addLast(num);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public int poll() throws InterruptedException {
lock.lock();
try {
while (queue.size() == 0) {
notEmpty.await();
}
} finally {
lock.unlock();
}
return queue.removeFirst();
}
}