HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 
Extending Java's Concurrency Model With Asynchronous Objects, by Francisco Morales

To extend Java's concurrent behavior in a more natural way, in a more object-oriented point of view, we propose an extension to Java's concurrency model that will emulate Eiffel's separate statement. (Eiffel is an object-oriented language with a comprehensive approach to software construction.) The extension permits the attachment of nonphysical processors or threads to objects, thus allowing them to behave in an asynchronous and completely independent manner. This article briefly shows the concurrency tools of the Java programming language, points out their shortcomings, proposes solutions, and ends with the implementation of a solution.

It's useful to evaluate Java's concurrent programming model by reviewing how this language implements the concepts explained by Bertrand Meyer and referred to as "The three forces of computation," which represent the statement: "To perform a computation is to use certain processors to apply certain actions to certain objects."

Java classes and objects play the same role in Eiffel as they do in Java. In Java the notion of a nonphysical processor fits in with the concept of thread as represented by the Thread class. A processor would then represent an autonomous thread of control that's capable of supporting the sequential execution of instructions on one or more objects.

Differences arise when we look at the possible assignment of processors to actions. In Java we have only the run() method, which belongs to the Runnable interface, for concurrent execution. This interface must be implemented by a class attached to a Thread instance in construction time and then, and only then, can we explicitly start the thread's execution, employing the technique known as delegation.

The problem originates from the fact that threads and objects represent different entities - methods run on threads, objects do not - so the only way to get a method to run concurrently (in an independent thread) is to call it from inside that thread's run() method. This is a disadvantage of this model since the object technology's basic model for computation would somehow be broken, as we can easily conclude from the following statement:

objRef.operID(args);

Here the execution of method operID(), called as part of an action performed on a client object, won't occur concurrently as long as it's not placed inside a thread's run() method. The model is said to be broken since there's no natural, object-oriented way in Java to provoke the attachment of different streams of execution to both the client object and objRef. The entire class design should be oriented to the implementation of the Runnable interface and the creation of Thread class instances.
Figure 1 shows, in UML notation, a possible general pattern for this problem.

Figure 1

The benefits of this mechanism are based on the fact that Java clearly separates the notion of data abstraction, implemented by classes, from the notion of control, represented by the framework that's composed of the Thread and ThreadGroup classes and the Runnable interface. Table 1 shows the main components of this framework.

Table 1

Once this model has been analyzed, let's proceed with Eiffel's concurrency model.

A Closer Look at Eiffel's Model
We'll now analyze Eiffel by looking for features that are capable of improving the former concurrency model. According to Meyer: "... any object O2 is handled by a certain processor, its handler; the handler is responsible for executing all calls on O2 (all calls of the form x.f (a) where x is attached to O2)."

From this statement we can conclude that the proposed model has two different call semantics:

  • Synchronous: The client object will be forced to wait for the server to complete its operation, as requested in the code.
  • Asynchronous: An object doesn't need to wait for the others to proceed with its execution, since it occurs in different processors.

In Java the default semantic is obviously synchronous. It doesn't provide any syntactic construction in its classes that will specify a different processor for its methods. So we need to define asynchronous execution through the use of Thread instances and the method run. This is the only way to define an asynchronous split of control.

Defining Separate Entities in Java:
A Straightforward Implementation
The essential difference between sequential and concurrent execution of actions is that the handler of the call's target is different from the one that originated the call. Doug Lea's interactive diagrams show this difference with solid and dashed lines that represent synchronous and asynchronous method calls. Figure 2 depicts such an interactive diagram for the pattern in Figure 1.

Figure 2

To implement this difference in Java, we'll follow Eiffel's approach, which allows us to define separate entities in two ways. The first one permits the creation of an object's instance and attaches it as an independent processor.

x: separate A

Here we're declaring an object x, instance of class A, that you'd execute on a different processor.

The second approach takes a static form and is applicable when all the instances of a class are intended to be separate entities:

separate class A ...

We cover both forms in this article. Our semantics require a new processor to handle all the messages for each of the instances of our separate classes or objects.

Obviously, we need to modify the Java syntax, adding a new keyword as an optional modifier in class and field definitions. To accomplish this, a preprocessing approach is proposed. This means that a parser program should be developed for parsing the Java source file and eliminate, if included, the separate modifier and replace it with some standard code.

The following is our first approach to the problem, but not the best one. We'll add some code before any invocation to a public method, and in separate-declared classes replace every method's body with code that's responsible for the creation of a new thread and the execution of its run() method. Inside this method we'll select the original code and invoke it. That's not the exact semantic of separate in Eiffel, but it provides us with a good place to start our evaluation. Since we need to intercept method invocation, we use the Java Reflection API.

Java Tools for the Solution
The Reflection API is composed of a set of classes that enable us to discover information about Java classes at runtime. This new feature of Java, developed with version 1.1 of the JDK, has also been called the introspection API because it gives objects the ability to look inside themselves or other classes during runtime.

The API defines the following elements:

  • At the core of the API is an object called Class for reflecting classes and interfaces in a running Java application.
  • Every object has a constructor, can perform a set of actions (also called methods), and is characterized through a number of attributes or variables. All these components are reflected through the Constructor, Method, and Field objects in the API that permit us to obtain information about each object's elements according to the security police used.
  • The Member interface, implemented by the three former objects, contains the prototypes of methods that allow you to query the object's members.
  • To represent primitive Java data types, the API contains nine Class objects defined as constants such as java.lang.Boolean.TYPE and java.lang.Character.TYPE.
  • There are two utility classes: Array and Modifier. The former allows us to access and construct Java arrays dynamically; the latter helps to decode language modifiers on classes and members.

Let's now proceed with our desired output definition on the basis of these elements.

Implementing the Solution
Separate as a Class Modifier
Let's now define how our solution should work, what we need as input data, and what we should produce. First we have a class like the one in Listing 1. After preprocessing, we obtain the code in Listing 2.

Two things must be taken into account:

  1. The security police used
  2. The methods' formal parameters

The security police that are used could affect the way we obtain the original method references, the one we invoke later in method run(). In Listing 1 we defined the method m2() as public, since the getMethod() method just returns Method objects that reflect the specified public member of the class. To obtain a reference to members affected by private-, protected-, or default-access modifiers, like m1(), for example, we can take one of the two approaches listed below:

  1. We can use the getDeclaredMethod(...) method instead of getMethod(), which can throw a security exception when a security manager is installed.
  2. As in Listing 2, the original modifiers on the methods generated by the preprocessing parser, m1() and m2(), remain untouched, and the original programmer's methods in the generated code, orig_m1() and orig_m2(), get declared public. This approach shouldn't affect the security of the application, since orig_m1() and orig_m2() are completely new for the program and invocations on them don't exist.

The second item to take into account is the methods' formal parameter treatment that must be done. The reflection API allows us to refer them at runtime through an array of Object instances, the __params variable used in Listing 2. Because of this, you shouldn't declare any parameter belonging to some of Java's primitive types. Instead we propose the use of the corresponding wrapper classes found in the java.lang package such as Integer, Float, and Double.

Separate as a Modifier for Class Fields
This solution is a little bit trickier since we'll find situations where the object's instances should run in their own thread and others where this behavior won't be needed.

To accomplish this, we've implemented the desired output in an inner class whose role is to permit the execution of each method's invocation in an object's thread. We use the starting code in Listing 3, then after preprocessing we should obtain the code in Listing 4.

As you can see, the changes affect classes A and B. The _Aux class in A encapsulates the behavior to execute in the separate case. Each invocation of m1() in B should be replaced with a separate_m1().

Another detail to take into account is the use of the getDeclaredMethod() method, instead of the getMethod() mentioned earlier, for taking a reference to the method that will be invoked. The main reason behind this decision is that we're invoking methods of A from class B, so we can't make them all of public type.

One more thing: the solution of encapsulating the separate behavior in an inner class is perfectly portable to the first analyzed case. The difference is just in the semantics of the problem.

Implementing the Preprocessing Parser
Traditionally, as everyone knows, a parser is a tool capable of performing three basic tasks:

  1. It receives some text from an input source, a Java program, for example.
  2. The text has to be organized according to some predefined criteria.
  3. Some actions have to be performed on this input based upon the above-generated organization.

The first two tasks are frequently encompassed and performed by an auxiliary application named scanner. Its main objective consists of building high-level language units called tokens, while discarding constructs that don't represent any useful information, such as white spaces and comments. Figure 3 depicts this functionality.

Figure 3

The actions mentioned in Step 3 try to match the tokens generated by the scanner against a language specification. If this match becomes successful, the parser then performs some action upon the construct. In our case it could generate the code that implements the separate behavior.

Building a parser from the ground up is a highly complex and time-consuming task. We used a parser generator, namely Sun's Java Compiler Compiler version 1.0, or JavaCC, which can be freely downloaded from Sun's Web site or from Metamata.com. The last one offers the parser generator and a set of example grammars.

A parser generator is usually a tool that's capable of generating parser and scanner programs starting from a grammar specification of the language to one we want to parse. The Java grammar we used was one of the samples that ships with JavaCC from Metamata.com, which covers Java's 1.1 language extensions.

JavaCC is based on ANTLR technology; also, it is an LL(k) predicate-based parser generator. It is beyond the intention of this article to describe in depth the details of the parser generator's algorithms. The interested reader should note the references at the end of this article.

Changes to the Java Input Grammar
To correctly parse a Java program whose objects can be affected by the separate modifier, first modify the Java grammar that's used as input for the generation of the parser and scanner tools. For this purpose there are only a few elements to modify from the original grammar construct:

  • The set of reserved words and literals that have to include the token separately
  • The set of class and class field modifiers that allow separate to be a valid modifier for both elements

These changes are very simple since the specification language to build the input description is Java-based with a few extensions to specify grammars (see Listings 5 and 6).

Note: There's a special method for analyzing a class field, instead of a general method for fields belonging to classes or interfaces. That's because the interface's fields may not be declared separate.

Output Generation
The generation of the output code, in case of separate behavior, is straightforward. In the JavaParser class generated by JavaCC, when the separate modifier is found, we start storing the class elements defined by the programmer since the parser just analyzes language units, eliminating the separate token. At the end of this analysis the stored code is added to the one needed for separate behavior, which is generated by the class JavaParserNewCode.

To accomplish this generation, the only things to take into account are the method definitions, since separate behavior consists only of executing every method invocation in its own processor.

JVM Scheduling vs Eiffel's Concurrency Control Files
We've defined processors as abstract concepts, independent of the underlying hardware and operating system architectures. In doing so, we've achieved a desirable independence for physical details. Our object system can run on a single processor workstation with a time-sharing schedule or in an SMP server.

Eventually we need to assign our physical resources to the processors of our program. In Eiffel we have the Concurrency Control File as a mechanism for defining this mapping.

In Java it's impossible to define such things programmatically. Threads are created dynamically in runtime as any other object, and the scheduling is left to the Java Virtual Machine (JVM). We can use groups of threads and priorities to obtain some level of control over the execution of threads, but the core algorithm is embedded in the JVM and, in current versions of the JDK, it's not possible to customize or substitute. In fact, it's somewhat implementation-defined (as noted in earlier versions of JDK, Solaris and Windows implementations differ in that the first is not preemptive).

In Java, distributed programming uses an additional API, called RMI, for communicating remote JVMs via remote interfaces. Alternatively, we can use IDL and CORBA.

The JVM is an operating system process in current commercial OSs, so they're under control of the OS process control. We haven't heard about parallel implementations of the JVM, exploiting the runtime information about threads, but it's clear that it would be advantageous to obtain better performance in architectures that include multiprocessing.

Conclusion
We've described our experiences and related studies regarding the introduction of Eiffel's concurrency model into Java. Our goal has been to extend Java's concurrent programming capabilities with the creation of completely independent objects through the use of a separate modifier.

Our approach helped us to unify, in some way, the notion of objects and threads, giving a more consistent object-oriented view to the design and implementation of concurrent applications in Java. The newly introduced model keeps the conceptual architecture strictly separate from the physical one; the former assumes that there'll be as many resources as the application needs; the latter is responsible for the creation of threads and their assignation to separate objects.

Another benefit to this approach is applicable to the academic field; we've taught Concurrent Java Design for three years and lacked a uniform object-oriented-based vision of the problem. With our extension, the design of concurrent Java applications is divided into two stages. The first is based on objects that are affected by the separate clause in which we apply the usual object-oriented execution mechanism of object.operation(args) and introduce the concurrency concepts in a more natural way. In the second stage we present Java's concurrency tools in more detail, going deeper into its mechanism and behavior.

Through this division, new concepts become simpler and more natural for the student since the move from sequential to concurrent programming just represents, initially, a little change in processor assignation.

One problem is the management of distributed resources. In Eiffel resources are mapped to processors through the Concurrency Control File, which enables you to manage the allocation of local and remote resources to processes. This unified view of mapping creates a better plan for running threads, allowing applications to control the physical allocation. The only ways for thread control in Java are, as mentioned previously, the Thread and ThreadGroup classes and the methods inherited from the Object class. These elements have to be combined with the functionality contained in the RMI package when we're developing distributed multithreaded applications. This makes this work error-prone and creates hard-to-predict client machine dependencies. Hence, we're preparing an extension of Java's thread control capabilities with support for distributed resources management.

References

  1. Lea, D. (1997). Concurrent Programming in Java. Design Principles and Patterns. Addison-Wesley.
  2. Meyer, B. (1997). Object-Oriented Software Construction. Prentice Hall.
  3. "ANTLR: A Predicated-LL (k) Parser Generator." Software - Practice and Experience, Vol. 25(7), 789-810. July 1995.
  4. Voss, G., and Parr, T. "Parsers, Part I." (http://developer.java.sun.com/ developer/technicalArticles/Parser/SeriesPt1/index.html). January 1997.
  5. Voss, G., and Parr, T. "Parsers, Part II. Building a Java Class Browser." (http://developer.java.sun.com/developer/technicalArticles/Parser/SeriesPt2/index.html). January 1997.
  6. Parr, T., and Coker, J. "Parsers, Part III. A Parser for the Java Language." (http://developer.java.sun.com/developer/technicalArticles/Parser/SeriesPt3/index.html). March 1997.
  7. Stanchfield, S., and Parr, T. "Parsers, Part IV. A Java Language Cross-Reference Tool."
    (http://developer.java.sun.com/developer/technicalArticles/Parser/SeriesPt4/index.html). December 1997.
  8. Joy, B., Steele, G., Gosling, J., and Bracha, G. The Java Language Specification. Addison-Wesley.

Author Bio
Francisco Morales, an assistant professor at the Pontifical University of Salamanca in Madrid, is also an independent software consultant. He holds a B.Sc. and M.Sc. in mathematics from Dresden's Technological University in Germany.
[email protected]

	


Listing 1: Sample class code for later processing

separate public class A {
	void m1(){
  		//... m1's code
	}

	public void m2(Integer x) {
		// ... m2's code
	}

   // ... Other class' code 
}

Listing 2: A straightforward implementation of separate behavior attached statically to a class

import java.lang.reflect.*;

public class A implements Runnable {
 // Data needed for each method invocation
 private Method _method;	   // Method to invoke
 private Object[] _params; // Method's parameters
 private Object _ret;	   // Return value

 public void run(){
  try {
	 _ret = __method.invoke(this,_params);
	} catch(Exception e){
	 System.out.println("Error: " + e.getMessage());
	};
 }

 // Methods to execute the separate way 
 synchronized public void m1(){
  Class [] FormalParams = new Class[0];
	_params = new Object[0];
	try {
    _method = this.getClass().getMethod("orig_m1", 
                                     FormalParams);
   } catch(NoSuchMethodException e){
		System.out.println("Error: " + e.getMessage());
	};
   new Thread(this).start();
	return;
	}

 synchronized public void m2(Integer x) {
  Class [] FormalParams = { x.getClass() };
   _params = new Object[1];
   _params[0] = x;
   try {
     _method = this.getClass().getMethod("orig_m2", 
                                     FormalParams);
   } catch(NoSuchMethodException e){
     System.out.println("Error: " + e.getMessage());
	};
  	new Thread(this).start();
	return;
 }

 // The original programmer's methods
 public void orig_m1(){ 
 		// ... m1's code
 }

 public void orig_m2(Integer x){
  		// ... m2's code
 }
 
  // ... Other class' code 
}

Listing 3: A Java class applying separate to a class's field

class A {
	public void m1(){
  		//... m1's code
	}
}

class B {
   public static void main(String[] args) {
  	   separate A _a = new A();
		_a.m1();
	}
}

Listing 4: Separate behavior implementation for a class's field

import java.lang.reflect.*;
class A {
	// Elements for SEPARATE behavior attached to 
  //  fields.
	synchronized public void separate_m1()  {
	 try {
		new _Aux.callMethod(this.getClass().getDeclaredMethod("m1", new Class[0]), 
                         new Object[0]) ; ???FMA - This line is too long !!!
   } catch(Exception e) { 
     System.out.println("Error: " + e.getMessage()); 
   }
	}

   public class _Aux implements Runnable {
		private Method _method;
		private Object[] _params;
		private Object _ret;

		public void callMethod(Method _method, 
                            Object[] _params)  {
			this._method = _method;
			this._params = _params;
			new Thread(this).start();
		}

		public void run() {
			try {
    			_ret = _method.invoke(A.this,_params);
	 		} catch(Exception e){}
		}
	} // End of the inner class
	
	// Original class code
   	public void m1() {
// ... m1's code
	}
}

class B {
  public static void main(String[] a) {
	A _a = new A();
	_a.separate_m1();
  }
}

Listing 5: Separate added to the set of the language's reserved words and literals

TOKEN :
{
... // All Java tokens plus ...
| < SEPARATE: "separate" >
}

Listing 6: Changes made to the Java language grammar
... 
void TypeDeclaration() :
{}
{
  LOOKAHEAD( ( "abstract" | "final" | "public" |
               "separate" )* "class" ) 
  ClassDeclaration()
|
  InterfaceDeclaration()
|
  ";"
}

void ClassDeclaration() :
{}
{
  ( "abstract" | "final" | "public" | "separate" )*
  "class" <IDENTIFIER> [ "extends" Name() ] 
  [ "implements" NameList() ]
  "{" ( ClassBodyDeclaration() )* "}"
}

void ClassFieldDeclaration() :
{}
{
  ( "public" | "protected" | "private"  | "static" | 
    "final"  | "transient" | "volatile" | "separate" )*
  Type() VariableDeclarator() ( "," 
         VariableDeclarator() )* ";"

  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.