MethodThread Package for Java
Version 3.0 (Released July 29, 1998)
MethodThread?
MethodThread
MethodThread written?
MethodThread?
MethodThread Work?
MethodThread?
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?.
MethodThread
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.
MethodThread written?
MethodThread class for a couple of reasons,
some personal preference and some in an attempt to remove limitations
in the current implementation of Thread.
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.
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).
MethodThread?
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.
MethodThread Work?
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:
Method object representing the
desired method, and the arguments to the method.
Constructor object representing the desired
constructor and the arguments to the constructor.
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.
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.
MethodThread incorporates some improvements
over earlier versions. This section describes the changes in more detail
than above.
MethodThread has been redesigned to be more general and
provide better access to its functionality. The code for finding
a method to invoke has been moved into its own class,
ReflectiveInvocation and may be used
separately. Further, the MethodThread class now executes
an instance of a class implementing the supplied
RunnableWithResult interface, which can be provided by
the user.
java.lang package. It is inside
its own MethodThread package. This was chosen because
ca.ualberta.cs.stevem is neither descriptive nor
aesthetically pleasing.
MethodThread class are now created
via a set of static methods rather than constructors.
getException() has been added to allow the user
to check for any exceptions thrown by the reflectively invoked method.
Class
for the class the method is defined on. Earlier versions of this class
required an instance of a class for both static and instance methods.
Note, though, that if a target object is specified that the search for
the best method includes both instance and static methods, which is
consistent with Java dispatch semantics.
Class representing the desired class to instantiate.
null
in the argument list where possible. This is really a fix for a bug
that was uncovered in implementing the new design.
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.