Java Threads and SugarCubes1
Frédéric BOUSSINOT, Jean-Ferdy SUSINI
EMP-CMA INRIA/Meije
2004 route des Lucioles
F-06902 Sophia-Antipolis
Frederic.Boussinot@sophia.inria.fr
Jean-Ferdinand.Susini@sophia.inria.fr
October 1998
Abstract
Keywords: Concurrency, Java, Threads, SugarCubes
1 Introduction
The reactive approach promotes the use of SugarCubes[BS] which is a set of Java[GJS] classes for concurrent reactive programming. The goal of this text is to compare SugarCubes with the standard threading features of Java, and to justify its use.
SugarCubes defines reactive instructions and reactive machines for concurrent reactive programming in Java. A reactive instruction is a kind of thread (one should call it a "logical thread") describing a behavior and its associated state. The difference with threads is that a reactive instruction is intended to be executed by a reactive machine, not by a thread scheduler. More precisely:
Of course, the example could appear as a rather academic exercise as one can simply get the circular behavior without using any concurrency at all. But a sequential solution would clearly be less modular and less flexible than a concurrent one. Anyway, the goal of this text is to compare two solutions to deal with concurrency, not to compare concurrency and sequentiality.
Finally, although the considered example is very simple, we feel that it is sufficient to enlighten the main characteristics and problems of the two approaches.
The text has the following structure: SugarCubes is briefly described in section 2. The example and its coding using threads are given in section 3. Section 4 contains the SugarCubes coding of the example. Finally, in section 5, one briefly considers the SugarCubes possibilities of fine control over reactive instructions and the use of broadcast events.
The Java threading mechanism, described for example in [GJS] or in [OW], is assumed to be known by the reader.
The codes described in this text are available on the Web2.
2 SugarCubes Overview
The aim of the reactive approach is to propose a flexible programming of reactive systems, especially those which are dynamic (that is, the number of components and their connections are changing during execution). Information on this approach can be found on the Web3. SugarCubes implements the reactive approach in Java.
The two main notions of SugarCubes are reactive instruction whose semantics refer to instants, and reactive machine whose purpose is to execute reactive instructions in an environment made of instantaneously broadcast events.
2.1 The Class Instruction
The class Instruction implements reactive instructions
which can be activated (method activ), reset (method reset) , or forced
to terminate (method terminate). Each activation returns as result one
of the three following values:
The basic reactive instruction of SugarCubes are:
Basically, a reactive machine detects the end of the current instant, that is when all parallel instructions of the program are terminated or stopped. The behavior is as follows:
3 Coding with Threads
Now, let us code in Java the example described in the introduction. Consider the Java class for moving a small icon following a sinusoidal path:
Two threads are defined, which cyclically call the sine and cosine methods. Code of SinMove is (CosMove is similar, except that cosine replaces sine):
Finally an applet is defined which concurrently runs the two previous threads:
How does the applet behave on screen? Naively, one expects that the icon would draw a circle, as a natural combination of sine and cosine moves. Unfortunately, this is not the case: with a cooperative platform, the first executed thread of SinMove or CosMove would never give up the control and the icon would not move at all on screen (the graphical thread would be starved); with a preemptive platform, one would get arbitrary moves, depending on the way threads are scheduled.
3.1 Use of Yield
The natural solution to let the applet run on cooperative
platform is to call the yield() method in SinMove and in CosMove, to let
them give up control. The method run of SinMove becomes (CosMove is changed
in the same way):
Now, the applet does not starve with cooperative platforms. Moreover, one gets a circle with cooperative platform, as sine and cosine are executed in turn. Thus, one obtains a very simple solution for cooperative platforms. Unfortunately, it does not work with preemptive ones, as yield() calls do not prevent the platform from scheduling tasks in an arbitrary way.
3.2 Synchronization
The two threads SinMove and CosMove must be synchronized
to execute in turn, independently of the preemption actions performed by
the platform.
The standard solution in Java consists in using the two wait and notify primitives. It is as follows: first, SinMove takes a lock on the shared icobj object; then, it executes the sine method and notifies the other thread; finally, it waits for a notification coming from the other thread which behaves in the same way. Code for SinMove is:
Now, one gets a solution which runs the two threads in turn, on both cooperative and preemptive platforms.
Please note that, as a side effect of synchronization, the shared object icobj becomes protected: sine and cosine are actually atomic actions. This can be an important matter when several threads can change the same icobj coordinate. Let us suppose for example that a second instance of SinMove is added. Then, if accesses where not atomic, the two instances calling sine could interleave their statements with an unforeseeable result.
This solution is however not satisfactory as the icon jumps unsmoothly from a point to another on the circle trajectory. The reason is that the graphical thread does not have enough time for processing the drawing orders. Actually, there is a lack of synchronization with the graphical thread. The problem is that Java does not give any way to synchronize with it !
3.3 Use of Sleep
One standard solution is to slow-down the user threads,
using sleep, to let enough time to the graphical thread to do its job.
Method run of SinMove becomes:
Now, the icon smoothly draws a circle. However, one must adjust the sleeping time to the machine execution speed (in the present case, 15 milliseconds, for a Sun ultra sparc 1 station). The solution is, thus, not portable.
3.4 Synchronous Updating
Another way to compensate the lack of synchronization
with the graphical thread, is to force an applet update after each move,
using the applet method update (which returns upon graphics update completion).
The program must be changed, to give SinMove and CosMove access to the
applet. SinMove becomes (CosMove is changed in the same way):
There is no more problem with the graphical thread as it has nothing to do now, except processing graphical events, of no use in this case.
3.5 General Solution
In the previous solution, the graphical update actions
are performed after each move. Unnecessary updates exist because, as SineMove
and CosMove are executed in turn, only one update over two is really needed.
A possibility would be to break the symmetry between SinMove and CosMove
and to let only one of them perform the update actions. But the problem
of redundant updates would not be definitively solved; it would, for example,
rise up again when animating two icons, instead of a single one. To solve
it in all cases, one can design a more general, but more complicated, solution
based on the following protocol:
Now, the system can be extended without problem to
several icobjs and the number of graphical updates is minimal.
As the corresponding code is more complicated than the one of the ad hoc solution of section 3.4, it is not given here.
4 Coding with SugarCubes
4.1 SugarCubes Code
SugarCubes is now used to code the example. As
applets are used, one first extends the class Machine and defines the new
class ReactiveApplet which implements Runnable:
The machine is cyclically activated (being its own execution context) while its program is not terminated and it updates the graphics at each instant.
SinMove is defined as an atom which action consists to call sine (atom CosMove which calls cosine is defined exactly in the same way):
The reactive instruction which cyclically executes atom SinMove is defined by:
new Loop(new Seq(new SinMove(icobj),new Stop()))
It is the counterpart of:
for(;;){ icobj.sine(); Thread.yield(); }
The applet SinCos now performs the following actions: a reactive applet mach is first defined; then, two reactive instructions are added to the reactive applet; finally, the reactive applet is started:
4.2 Comparison with Threads
Comparison with the thread-based code of section 3 is
as follows:
The syntax of SugarCubes instructions is not very friendly, as it forces the programmer to use instruction constructors; the aim of Reactive Scripts[BS2] is to give a simple and compact syntax which is easily translated into SugarCubes. For example, the previous example is typed as a reactive script as:
loop { icobj.sine() }; stop end || loop { icobj.cosine() }; stop end
4.3 Benchmarks
We have performed some benchmarks on the previous codes
running on several platforms (Sun UltraSparc 1 and 10, Macintosh G3, and
Linux on a PC Pentium 2 Pro). We have computed the number of frames per
second (FPS) performed by the system during execution (the instrumented
code used is available on the Web). Moreover, for the general thread-based
solution and for the SugarCubes one, the cases of 2 and 3 icobjs
have been considered.
Results are highly dependent of graphics orders being processed or not. Globally, when graphics orders are processed, SugarCubes is faster on Sun platforms (both 1 and 10) and very close to the generic thread-based solution on Linux. When graphics orders are not processed (programs are not executed as applets and method update does nothing), SugarCubes are, on Sun, slower with one icobj, but faster with two or three; on Linux, SugarCubes is always slower than thread-based solutions. Measures on Macintosh differ completely with the two versions of the MRJ used (2.0 and 2.1 alpha).
All these results are rather difficult to interpret and we feel that more work has to be done on that subject, including other examples. Moreover, the new version of the SugarCubes which is in preparation should be faster than the current version 1, and comparison should be continued when available.
5 Control over Execution
5.1 Terminating a Reactive Instruction
SugarCubes gives the programmer a complete control
over the execution of a reactive instruction. For example, reactive instructions
can be forced to terminate using the Until preemption operator. This operator
is actually the counterpart4 of Thread.stop
which is a source of problems and is deprecated in the new version 1.2
of Java (see [JSD] for details).
Let us suppose that one wants to reverse the icobj move direction from inverse clock-wise to clock-wise. Two new methods are introduced in Icobj to reset the angles, and to move backward:
Two atoms ResetAngle and InvSinMove are also defined to call the previous methods. Now, to reverse the icobj trajectory becomes possible with the following reactive instruction:
On generation of the event change, the loop executing SinMove is abandoned and ResetAngle is executed; then, the Until instruction is terminated and control immediately goes to the second loop executing InvSinMove.
Please note that reactive statement preemption is structured: the Until instruction can only preempt its body (the first loop). This is certainly a good point compared to the lack of structure of Thread.stop() with which one can preempt any thread as soon as a reference on it is available.
5.2 Broadcast Events
SugarCubes provides the programmer with a powerful
communication based on broadcast events. To show the power of broadcast,
let us consider a system made of two icobjs and suppose than one wants
to simultaneously reverse their trajectory. The solution with broadcast
events is extremely simple and natural: basically, there is nothing to
do! the two reactive instructions that pilot icobjs share the same event
change which is broadcast; thus, the two trajectories change at the very
same instant, when change is generated.
Using the local declaration instruction EventDecl, one can restrict the scope of event change, and, for example, invert the trajectory of only one of the two icobjs.
Event broadcast is modular. Suppose that new activities can be dynamically introduced into the system. Then, there is no need to change anything when a new instruction becomes active, as it automatically gets the same vision of events than the others instructions have (provided, however, that introductions of new instructions take place at the very beginning of instants). Modularity also allows the programmer to introduce "observers" (instructions that do no generate any event) in a system with the guaranty that it will have no effect at all on the others components.
6 Conclusion
6.1 Difficulties of Thread Programming
Threads programming is a difficult exercise, in which
one must deal with several overlapping notions, such as priorities, scheduling
policies, or accesses to shared resources. Basically, one can identify
two main problems:
The first problem is that a thread-based system must run on BOTH preemptive and cooperative platforms. Adding yield() calls is necessary for cooperative context, but does not solve the problem with preemptive ones: with preemptive platforms, yielding actions issued from the system and from the program are inextricably mixed; it is like if somebody behind the scene was dropping yielding actions in a totally uncontrolled (and may be unfriendly) way. To change this situation and regain control over preemptive platforms, programmers have to implement complex strategies. Programming becomes over-complicated and programmers must be very conservative and cautious to control all consequences of possible switches of control.
The second problem appears when threads need to communicate; for this purpose, threading systems only offer low-level primitives based on the notion of a lock. Thread programmers often try to overcome the problem by limiting communications and synchronizations as far as possible. Actually, they try to limit at the maximum the use of concurrency which leads to monolithic, rigid, and non-modular programming.
6.2 Benefits for Using SugarCubes
The semantics of SugarCubes code does not depend
on arbitrary choices made by the reactive machine executing it. Semantics
is completely captured by the code. This is a deep difference with threads,
the semantics of which depends on the underlying execution platform.
Coding with SugarCubes is actually very close to coding assuming a cooperative platform. Scheduling is basically performed "by hand" (using Stop, the counterpart of yield) and the programmer has to control how concurrent activities are fragmented. This leads to deterministic executions with a clear semantics. Cooperative strategies lower the needs of synchronizations to implement atomic accesses to data.
Using the preemption operator of SugarCubes, termination of reactive instructions can be forced in a completely safe way; the preempted instruction is allowed to finish the current instant and is thus in a stable state when preemption takes place.
SugarCubes offers a powerful and modular communication mechanism based on instantaneously broadcast events. This frees the programmer from always re-implementing high-level communication mechanisms each time threads must communicate.
SugarCubes is a simple and structured approach to concurrency which can be seen as an alternative to Java threads, especially when dynamic combinations of communicating concurrent components are needed. At least, it provides a useful technique complementary to the one of threads.
Acknowledgments
Thanks to Laurent Hazard, Jean-Pierre Paris, and Jean-Marc Tanzi for their remarks on a previous version of the paper.
References
[BS] F. Boussinot, J-F. Susini, The SugarCubes Tool
Box - A reactive java framework, Software Practice & Experience,
28(4), 1531-1550, December 1998.
A research report is available at: http://www.inria.fr/meije/rc/SugarCubes/
[BS2] F. Boussinot, J-F. Susini, The SugarCubes Tool Box - Rsi-Java Implementation, research report available at: http://www.inria.fr/meije/rc/SugarCubes/
[GJS] J. Gosling, B. Joy, G. Steele, The Java Language Specification, Addison-Wesley, 1996.
[Ho] Allen Hollub, Programming Java threads in the real world, Part 1, JavaWorld, September 98, available at: http://www.javaworld.com/jw-09-1998/jw-09-threads.html
[JSD] JavaSoft, Why JavaSoft is Deprecating Thread.stop,
Thread.suspend and Thread. resume, JavaSoft Documentation, available
at:
http://java.sun.com/products/jdk/1.2/docs/guide/misc/threadPrimitiveDeprecation.html.
[OW] Scott Oaks, Henry Wong, Java Threads, O'Reilly, 1997.
2 http://www.inria.fr/meije/rc/SugarCubes/ComparisonThreadsSugarCubes/
3 http://www.inria.fr/meije/rc/
4 Unfortunately, there is a kind of name clash: Thread.stop forces the termination of a thread, although in SugarCubes Stop only ends execution for the current instant. Actually, the counterpart of Thread.stop is obtained with the Until preemption reactive instruction.