Monitor Programming in JR
Monitor Programming in JR
Join the DZone community and get the full member experience.Join For Free
Java-based (JDBC) data connectivity to SaaS, NoSQL, and Big Data. Download Now.
Like I promised, I will restart my series about JR now that the evaluation period is over. After seeing how to develop using the JR programming language, we’ll see now how to use monitors in JR.
Monitors provide a higher-level abstraction than semaphores and produce better code with several advantages :
-
The mutual exclusion is implicit with monitors. Only one process is allowed in the monitor, so all the methods are automatically guarded with synchronization code. The synchronization between threads is made using signaling system, with condition variables..
Now we’ll see how to use them in JR.
In JR, you don’t have the possibility to create monitors directly, but there is a preprocessor that transforms a file with both JR and monitors operations into a plain JR file. This processor is called m2jr (monitor to JR) and is directly available in the JR distribution. So you only have to use the m2jr command to translate a .m file (conventional monitor extension file) into several JR files (one for the monitor and one for the condition variables). This file is normal JR with several comments to make the debugs easier (corresponding between lines in m and JR).
All the keywords of the m2jr language start with _ (underscore). To declare a monitor, it’s as easily as use the _monitor keyword :
_monitor MonitorTest {
//...
}
To add methods to the monitor, you just have to create method prefixed with _proc :
_monitor MonitorTest {
_proc void testA(){
//Some code
}
}
Only with that the mutual exclusion is guaranteed. Only one process is allowed into the monitor. If you want methods with a return type, you must use _return instead of return :
_monitor MonitorTest {
_proc void testA(){
//Some code
}
_proc int testB(){
_return 1;
}
}
To declare condition variables, the keyword is _condvar. You don’t have to initialize them, just declare them :
_monitor MonitorTest {
_condvar condVar1;
_condvar condVar2;
}
To use global variables, you must prefix them with _var :
_monitor MonitorTest {And to make operations on condition variables, you have to use _signal and _wait methods inside a proc method :
_var var1;
_var var2;
}
_monitor MonitorTest {
_condvar a;
_condvar b;
_proc void test(){
_wait(a);
//Some computations
_signal(b);
}
}
And you compile that to JR using the simple commmand :
m2jr MonitorTest.m
That will create two files (MonitorTest.jr and c_m_condvar.jr). Of course, you can use this monitor in a normal JR class, you don’t have to compile classes who use monitors with m2jr, only with the JR compiler. There is just a single thing to worry about. m2jr generates a constructor that take a String in every monitors class, you when you instantiate the monitor, you have to provide a String representing its name as the first parameter. And if you want to create a new constructor in a monitor, you just have to call super with a String. We’ll see an example later.
Before going further, we must have more informations about the signal operations. When writing monitors, you.
By default, m2jr make the compilation with SC, but you can configure it to use other philosophies. Just add the abbreviation of the philosophy (lower case) as an option to the m2jr compiler. By example, to compile using Signal & Exit :
m2jr -sx MonitorTest.m
The main differences, is that the SC create a signal stealers problem. You will quickly understand with an example. With what we know now, we can create a monitor to manage a bounded buffer :
_monitor BoundedBuffer {
private static final int N = 5; //Size of the buffer
_var String[] buffer = new String[N];
_var int front;
_var int rear;
_var int count;
_condvar notFull;
_condvar notEmpty;
_proc void deposit(String data){
while(count == N){
_wait(notFull);
}
buffer[rear] = data;
rear = (rear + 1) % N;
count++;
_signal(notEmpty);
}
_proc String fetch(){
while(count == 0){
_wait(notEmpty);
}
String result = buffer[front];
front = (front + 1) % N;
count--;
_signal(notFull);
_return result;
}
}
A thing that some of you will certainly find weird is the loop around the wait operation. Is to avoid the signal stealers problem. If there were not while loop, imagine that situation in SC :
- The thread 1 try to make a fetch(), there is no data, so it wait for the notEmpty condition variable
- The thread 2 make a deposit(), that awake the thread 1, but it needs to acquire again the mutual exclusion.
- Before the thread 2 has acquired the mutual exclusion, the thread 3 make a fetch(), there is enough data, so thread 3 make a fetch and get the data. So now, it’s empty.
- The thread 2 acquire the right to go into the monitor and get a data. But wait a minute, there is no data and it will fetch a null data or perhaps an old data still fetched depending on the current state of the buffer
To avoid that situation, you just have to wrap the wait in a while loop instead of a if and that’s done ! Or you can also use SW instead of SC.
With that monitor, we can easily solve the producer and consumer problem :
public class ProducerConsumer {
private static final int N = 12; //Number of producers and consumers
private static BoundedBuffer bb = new BoundedBuffer("Bounded Buffer monitor"); //The monitor
public static void main(String... args){}
private static process Producer((int i = 0; i < N; i++)){
bb.deposit("Producer" + i);
}
private static process Consumer((int i = 0; i < N; i++)){
System.out.println("Consumer" + i + " : " + bb.fetch());
}
}
That will provide output like that :
Consumer10 : Producer0
Consumer0 : Producer1
Consumer1 : Producer2
Consumer2 : Producer4
Consumer3 : Producer5
Consumer4 : Producer3
Consumer8 : Producer7
Consumer11 : Producer10
Consumer6 : Producer8
Consumer5 : Producer6
Consumer7 : Producer11
Consumer9 : Producer9
So it works well.
More than signal and wait operations, m2jr provide also others operations on condition variables :
- _signal_all : Awake all the process of the waiting queue. This operation is only provided in Signal & Continue mode.
- _empty : Test if the condition variable has any process waiting on it
- _wait(condvar, int priority) : Enqueue the process with the given priority. If you use that, the queue is now managed as a priority queue. You cannot use both wait without priority and wait with priority on the same condition variable
- _minrank : Return the min priority on the given condition variable. If the condition variable has no waiters, the returned given number doesn’t seem anything.
With all that stuff, you can create monitors to solve almost all concurrency problems like barber shop, philosopher dinner, kwai cross or a lot of others problem.
I hope you found that post interesting. The next post about JR will be about operations and capabilities.
From
Connect any Java based application to your SaaS data. Over 100+ Java-based data source connectors.
Opinions expressed by DZone contributors are their own.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}{{ parent.urlSource.name }}