The MethodThread Package for Java

by Steve MacDonald

Version 3.0 (Released July 29, 1998)

Contents

What is MethodThread?

The MethodThread package contains classes that extend the functionality of the Thread class and the Reflection API. In concert, these two extensions allow threads to be created to execute arbitrary methods or constructors, defined for public classes or instances of public class, with a set of arguments. Further, it is possible to get the results of the execution, either return values or exceptions thrown during the computation.

This package provides two classes to implement the above functionality. The first class, MethodThread, is a subclass of Thread that accepts a different kind of target object than those implementing the Runnable interface. One such target object is the other class provided in this package, ReflectiveInvocation. The ReflectiveInvocation class reflectively invokes a method or constructor that may be specified using an instance of Method or Constructor or, more importantly, by name. The extension to the Reflection functionality is that the search for a named method or constructor is based on matching the arguments but taking into account superclasses and interfaces. Currently, Java Reflection is only capable of searching for a named method or constructor based on an exact match of argument types.

It is important to note that these two classes are used in conjunction but are not tied together in any way. Each may be used individually. For more details, see How Does MethodThread Work?.

Getting MethodThread

You can retrieve the software in several different forms:

The distribution includes the source file for the MethodThread class, a README file which contains some additional documentation and the license, an example program and its output (which is really my test suite), and javadoc documentation. The example program also contains the expected output of the program on Sun workstation running Solaris 2.5 and a Linux box running Steve Byrne's JDK 1.1.5 Version 7 port. The last test case in the example program may have the threads executing in a different order, but otherwise the results should be identical on all machines. If you get different answers, please e-mail me (stevem@uwaterloo.ca) with details.

Why was MethodThread written?

I wrote the MethodThread class for a couple of reasons, some personal preference and some in an attempt to remove limitations in the current implementation of Thread.
  1. The single entry point style of programming is inelegant and restrictive. It forces programmers to implement their own protocol for invoking a method and retrieving results (not entirely unlike the stubs and skeletons generated by the rmic tool), which must be updated when the interface to a class is changed. The more general approach of allowing any method to be invoked removes some of this effort.
  2. The current thread facilities fail when considering the case where multiple threads are executed on the same target object, which is common in parallel programming. There are actually two facets to this problem:
    • The run() does not allow parameters to be given to a thread. Typically, when creating multiple threads on a single target object, each thread is responsible for a part of the computation and thus needs to be distinguished from the other threads, which is easily accomplished with parameters. While it is possible to accomplish this using a form of double dispatch (create a set of objects representing the parameters and start threads on theses objects, which use the original target to compute their results), this may require additional programming effort (particularly if there are multiple parameters).
    • It would be nice to allow a thread to return a result. This is true in general, but having multiple threads on a single target object makes this difficult. Implementing this requires a separate buffer where results can be written by the thread and read when the thread has finished.

What do you need to do to use MethodThread?

Most of the MethodThread package is a set of static methods used to create the threads. These methods cover combinations of thread parameters (thread group and thread name) and parameters for reflectively finding a method (target object or class, method name or instance of Method) or constructor (constructor name, class, or instance of Constructor), and for specifying the arguments to the method or constructor. These methods are createMethodThread() and createConstructorThread(), for creating threads executing methods and constructors respectively. The only restriction on these arguments is that the target object or class must be public, as must be the method or constructor the user wishes to execute. The lookup for the appropriate method or constructor to execute automatically handles conversion between primitive types and the associated wrapper classes as given in the Java Reflection specifications.

The only other changes to normal threads are the added methods joinWithValue() and getException(). The joinWithValue() method joins with the executing thread and returns the result of the invoked method (there are also versions of this method that will attempt to join with the thread for a given time interval, just like the join() method). The getException() method returns the first exception thrown during the execution of the invoked method.

It is important to note that this class is a subclass of the existing Thread class. This means that all the normal thread operations, such as join() should still work (although not all have been fully tested). Also, it means that the existing ThreadGroup class can also be used as a container. The only difference may be that the thread object will require a cast to invoke methods specific to the MethodThread class.

How Does MethodThread Work?

The MethodThread package provides the RunnableWithResult interface, which augments the Runnable interface by adding new methods for retrieving results and exceptions. The MethodThread class subclasses the Thread class to accept objects implementing this new interface as target objects. These new threads use the extended interface to retrieve the results of their computation.

This package also provides the ReflectiveInvocation class, which implements RunnableWithResult. This class reflectively invokes a method or constructor based on one of the following sets of parameters:

  1. a target object, a Method object representing the desired method, and the arguments to the method.
  2. a Constructor object representing the desired constructor and the arguments to the constructor.
  3. a target object or class, the name of the desired method, and the arguments to the method.
  4. the class or name of the desired constructor and the arguments to the constructor.
The ReflectiveInvocation class provides a set of methods that search for the most appropriate method or constructor for cases 3 and 4. These methods extend the Reflection capabilities by searching for the method or constructor whose arguments are the best match with the supplied arguments rather than exactly matching parameter types. Note that the functionality in the ReflectiveInvocation class is used to implement the primary purpose of this package, but is not tied to this purpose and may be use separately.

To facilitate the creation of threads executing arbitrary methods or constructors, the MethodThread class provides an extensive list of factory methods. These factory methods, used to create instances of MethodThread, accept thread-specific parameters as well as the parameters needed to create an instance of ReflectiveInvocation, which is used as the internal target object of the new thread.

A quick example

As a quick example, consider the following class:
    public class Test
    {
       public Object method(Integer a)
       {
            ...
       } /* method */
    } /* Test */

The following code fragment demonstrates how to execute a thread on the method method() on an instance of the class Test. For simplicity, this code only catches general exceptions rather than catching each individual exception that may be thrown.

    import java.lang.* ;

    public class Example
    {
       public static main(String[] argv)
       {
          Test obj = new Test() ;
          Object result ;
          Object args = new Object[1] ;
          args[0] = new Integer(5) ;

          try {

             // Create a thread that executes obj.method(args)
             MethodThread t =
                     MethodThread.createMethodThread(obj, "method", args) ;

             // Start the thread executing.
             t.start();

             // Join with the thread and retrieve the return value.
             result = t.joinWithValue() ;

          } catch (Exception e) {
               ...
          } /* try */
       } /* main */
    } /* Example */

In this example, the string "method" is matched against all public methods that can be invoked on the target object obj using the argument types, in this case a parameter that is an instance of Integer.

We can change the above to use Method objects as follows. If methodObj is an instance of the Method class and represents the method(Integer) method in class Test, then we can instantiate the thread as follows:

   MethodThread t = MethodThread.createMethodThread(obj, methodObj, args) ;

In the above fragment, it is verified that the number of arguments expected by the methodObj method are supplied by the args array. For now, there is no check to determine if the supplied arguments are compatible with the expected ones. This is done when the method is invoked by the thread, at which point it is not possible to throw exceptions in the application code. However, these exceptions can be retrieved using the getException() method.

One of the improvements in the class allows the user to optimize the case where several threads are created to execute the same method. Originally, this could only be done with the following code:

     MethodThread[] t = new MethodThread[numberOfThreads] ;
     for(int i = 0;i < numberOfThreads;++i) {
          args = ... ;
          t[i] = new MethodThread(target, "method", args) ;
     } /* for */
This code does a costly lookup to find the method method() defined on the object target each time through the loop. Assuming that the types in the args array remain constant so that the same method is always used, this can be optimized in Version 3.0 as follows:
     args = ... ;
     MethodThread[] t = new MethodThread[numberOfThreads] ;
     Method m = ReflectiveInvocation.findMethod(target, "method", args) ;
     for(int i = 0;i < numberOfThreads;++i) {
          t[i] = new MethodThread(target, m, args) ;
     } /* for */
This version performs only one reflective lookup. As stated earlier, the ReflectiveInvocation class is a separate class that may be used for looking up a method or constructor based on the name of the method or constructor and a set of arguments.

It is important to note that the arguments to the thread are not cloned but are references to the original objects. This means that any changes made via side effects are visible to the caller, just like normal method parameters. This was true of earlier versions as well. To ensure the consistency of the data, you will need to join with the thread before accessing the affected parameters.

Changes for Version 3.0

This version of MethodThread incorporates some improvements over earlier versions. This section describes the changes in more detail than above.

Caveats

There are some limitations in the current implementation. Any questions, comments, or bug reports can be addressed to stevem@uwaterloo.ca.

This package has only been tested with Java 1.1.x. I have not investigated the changes to reflection in Java 1.2, although I do know there have been some. As I understand it, Java 1.2 is a little less restrictive in regards to the visibility of the method to be invoked, and on the visibility of the class in which the method is defined. This package does not reflect those changes. This package should still work with public methods for instances of public classes. Other cases may work as expected in Java 1.2, particularly when the Method or Constructor object is used.

Because of the security mechanism within the reflection class, this package can only execute public methods within public objects. Presumably, if a new security manager was instantiated this could be eliminated.

The method search algorithm for matching method specified by name still does not re-implement Java dispatch semantics, although it is as close as it can reasonably get. The choice of method is a simple multiple dispatch mechanism, where the run-time type of the arguments is used to find the most appropriate method. Details of the algorithm can be found in the class documentation. Any ambiguities can still be overcome by specifying the Method or Constructor object itself rather than using the name.

When a Method or Constructor object is specified, the only check at thread creation is that the number of arguments match. If the arguments are not compatible, then the thread itself throws an exception, which is not passed to the user. This particular point comes down to a duplicated check; I can check the compatibility of the arguments at thread creation time, but this might be relatively expensive and it is duplicated when the method is actually dispatched. I'd appreciate any viewpoints on this one.

Arrays of primitives are be widened from arrays of wrapper objects. Arrays of primitives are actually objects in their own right, and can be passed as normal parameters in the argument array. However, the primitive arrays are not subject to any form of implicit casting (i.e. byte[] cannot be substituted for int[], although A[] can be substituted for B[] if A is a superclass of B).

The use of instances of Class to find static methods makes it a little harder to create an instance of this class that executes a method on an instance of Class in its own thread. This can be done by upcasting the instance of Class to Object. Now, the constructor that is invoked will use the Class instance as the target object. Similar machinations may be required to use instances of ThreadGroup and String.

If a method is overloaded with arguments for both a primitive and its associated wrapper class (i.e. f(int) and f(Integer)) and that method is looked up by name, the method with the wrapper class argument will always be taken. This results from the extra step required to convert the wrapper class to the primitive. Since the method with the smallest number of steps (or distance, as it is referred to in the package) is always selected, the primitive version will never be called. If it is important to invoke the method with the primitive argument, you will need to use the Method object.


Steve MacDonald