Introduction to JR programming language
1. Language overview
JR is a programming language especially created to solve concurrent programming problems. This language is an overview of Java who add to this last the main paradigms of concurrent programming. Moreover JR make easier the concepts still implemented in Java like process or semaphores. There is also several extensions for JR to add more functionalities like monitors and Conditional Critical Region (CCR). JR is the implementation of the SR language for Java.
JR makes nothing else than add a layer over Java. Once we use the JR compiler, the JR source files are transformed in Java files and are executed by the virtual machine like any other Java class.
JR is often used as a school support to learn concurrent programming.
In this article, we will see the bases of the programmation with JR.
The presented version is the one of June 2009, the version 2.00602 who are based on Java 6.0.
This article need that you've installed the JR environment on your system. An article is available here for the installation under Windows.
In this article, we will especially focus on the apports of the JR language for the concurrent programming. We will not see the the integrality of the language. JR has other benefits than make easier concurrent programming, but we will not see that in this article. Moreover, all the aspects of concurrent programming in JR will not be treated here.
2. Hello World
Like all other languages, we must start with a simple Hello World. Thus we'll create a file Hello.jr. Nothing special here, it's pure Java :
public class Hello { public static void main(String[] args){ System.out.println("Hello World"); } }
Then we can compile it :
jrc Hello.jr
This will create a jrGen folder containing Java files. The result of a JR compilation is always a set of Java files corresponding to the translation of the JR files.
To launch your JR program, use the jr command followed by the name of the main class (class containing the main method) :
jr Hello
That will display :
Hello World
The jr command will also launch the compilation of Java files. This compilation will be done every time. If you want to make only the launch of compiled files, you can use the jrrun command.
Like said in introduction, the JR language extends the Java language. So, you can code in Java with JR. Thus an Hello World is only Java.
3. Processes
The first thing to see is the declaration of processes. This is done in an easier way than in Java. No need to instanciate some objects, this is done in a declarative way and JR make the rest.
For the declaration of process, JR introduce a new keyword process who enable to declare a process. Here is the simplest declaration of a process :
process Hello { System.out.println("Processus");
Like you can see it, it's easier than in Java. And better, no need to launch it, you just have to instanciate the class. A process can also be declared static. This times, it will not be launched at the instanciation of the class but at the resolution of the class by the virtual machine. By example, we can rewrite an HelloWorld in that way :
public class HelloProcess { static process Hello { System.out.println("Hello World"); } public static void main(String[] args){}
who display exactly the same thing as the first version of the Hello World. At the difference that our display is made from a thread.
Moreover, JR enable to declare a big set of threads in a declaration with the following syntax :
static process Hello((int id = 0; id < n; id++)){}
This will declare n threads. The syntax is the same as the for loop. Let's declare 25 threads Hello World :
public class HelloProcess { static process Hello((int id = 0; id < 25; id++)){ System.out.println("Hello World from thread " + id); } public static void main(String[] args){}
When we launch that, we could have the following result :
Hello World from thread 2 Hello World from thread 24 Hello World from thread 11 Hello World from thread 22 Hello World from thread 0 Hello World from thread 4 Hello World from thread 6 Hello World from thread 8 Hello World from thread 10 Hello World from thread 12 Hello World from thread 14 Hello World from thread 16 Hello World from thread 18 Hello World from thread 20 Hello World from thread 23 Hello World from thread 21 Hello World from thread 19 Hello World from thread 17 Hello World from thread 15 Hello World from thread 13 Hello World from thread 9 Hello World from thread 7 Hello World from thread 5 Hello World from thread 3 Hello World from thread 1
Like you can see, if you launch several times the program, the display is not same and the message commes in an order completely different order at each launch. Nothing can guarantee the order of the threads launches and still less the order of the execution of the instructions and you must not count on it.
It's the basis of the concurrent programming. You cannot predict the order of the instructions in the different threads.
4. Quiescence action
JR introduce a new concept, really powerful, the quiescence action. It's an action who's executed when the system is quiescent. It seems that all the process are finished or remains in deadlocks.
Before that, we use to introduce the concept of operations. In our case, an operation is a simple method declared with the op keyword. But in JR an operation is more than a method and could be invoked in different ways and permit other things that methods, but this is beyond the scope of this article.
Here is a declaration of a simple operation.
public static op void end(){ System.out.println("End")
So, this is a simple method with the op prefix. You can invoke it like any other method. But you can also declare it like the action to execute when the system is in quiescence state. If we take our example of the 25 hello world and if we define the quiescence action here is what we get :
import edu.ucdavis.jr.JR; public class QuiescenceProcess { static process Hello((int id = 0; id < 25; id++)){ System.out.println("Hello World from thread " + id); } public static void main(String[] args){ try { JR.registerQuiescenceAction(end); } catch (edu.ucdavis.jr.QuiescenceRegistrationException e){ e.printStackTrace(); } } public static op void end(){ System.out.println("End");
We use the registerQuiescenceAction(op) method of the JR class. This class provide some utility methods for the JR programs
And a launch, we see something like that :
Hello World from thread 0 Hello World from thread 22 Hello World from thread 23 Hello World from thread 21 Hello World from thread 24 Hello World from thread 20 Hello World from thread 19 Hello World from thread 18 Hello World from thread 17 Hello World from thread 16 Hello World from thread 15 Hello World from thread 14 Hello World from thread 13 Hello World from thread 12 Hello World from thread 11 Hello World from thread 10 Hello World from thread 9 Hello World from thread 8 Hello World from thread 7 Hello World from thread 6 Hello World from thread 5 Hello World from thread 4 Hello World from thread 3 Hello World from thread 2 Hello World from thread 1 End
This is really useful to execute an action after the end of the system and verify something on the system. By example, display a message in the case of a deadlock or display debug informations on the executed operations.
5. Semaphores
We will now see how to use one of the basic concept of concurrent programming : the semaphores. The semaphores are a really simple concept but very powerful. A semaphore represent a certain integer valur representing the number of threads that can go in a certain portion of code, call it "s". A semaphore has two actions :
- P : make the thread wait while s equals 0 and then decrement s.
- V : increment s.
This two operations are atomic. We use the semaphores to protect a critical section who need to be executed in an atomic way. We can also use semaphores to restrain the number of threads that can execute some instructions in parralel.
The declaration and the use of semaphores is really easy. Here is how you can declare a semaphore with a initial value of 1 :
sem mutex = 1;
Then, the operations P et V are extremely simple to use :
P(mutex); //Critical section V(mutex);
The semaphores are principally used to solve the critical section problem. Imagine a simple example, but who show exactly the problem that the semaphore can solve :
private static int value = 0; static process Calculator((int id = 0; id < 50; id++)){ for(int i = 0; i < 5; i++){ value = value + 2; } }
Because this code launch 50 threads who add each one 5 times 2 to value, we could think that value must be 500 at the end of the execution, isnt'it ?
But nothing guarantee that result. This is known as the interleaving in concurrent programming. This is because the + 2 operation is in reality 3 operations :
- read the value of value
- add 2 to the read value
- set the new value to value
A thread can be put in wait just after 1 and do the incrementation on the old value but other threads have still made the incrementation, but it has the old value when it mades the +2 and write and false value to value. To prove that to you, execute the following code several times :
import edu.ucdavis.jr.JR; public class SemaphoreProcess { private static int value = 0; static process Calculator((int id = 0; id < 50; id++)){ for(int i = 0; i < 5; i++){ value = value + 2; } } public static void main(String[] args){ try { JR.registerQuiescenceAction(end); } catch (edu.ucdavis.jr.QuiescenceRegistrationException e){ e.printStackTrace(); } } public static op void end(){ System.out.println(value); } }
On my computer, i've the following results.
498 500 500 500 496
And if we use greater values, it's even worse. By example, with 100 threads and 100 iterations :
20000 19560 19912 19758 20000
But this problem can be solved with semaphores :
private static sem mutex = 1; private static int value = 0; static process Calculator((int id = 0; id &lt; 50; id++)){ for(int i = 0; i &lt; 5; i++){ P(mutex); value = value + 2; V(mutex) } }
With that, we have the guarantee that only one thread can do the incrementation at a time and make it atomic. Then all the executions will finish with a value of 500. But that of course impact the performances because instead of x threads who made operations in a parralel way, we've now only one thread at a time. The example with 100 threads and 100 iterations is really slow. We can improve performance using the mutex semaphore around the loop. But the performances are to be considered differently in each example. So we must use wisely the synchronization methods of threads.
6. Conclusion
So, we've now discover the main concepts of the JR programming language. Like you have see in this article, this programming language enable to make easier the use of concurrent programming concepts.
I hope that this article has been useful to you to discover the JR Programming Language, and why not, to learn and use this language.
Comments
Comments powered by Disqus