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
 

If you are experienced in using the POSIX threads package (pthread) in C or C++, one of the first things you may have noticed while learning Java is that thread semantics is one area where Java's designers did not try to emulate C semantics. The next thing you may have noticed is that the Java approach seems awkward in many circumstances.

For instance, consider the timer() method shown in Listing 1. You might want to add a method like this to a long-running application to reassure yourself that your Java Virtual Machine has not gotten hung. Since this method contains an infinite loop, it only makes sense to run it in its own thread. The POSIX threads model allows you to pass a function pointer and an argument pointer to the pthread_create() method. The specified function is executed in its own thread of control and the argument pointer can be used to pass data structures into or out of the method. If you are used to programming with this threads model, you would expect to be able to invoke the timer() method using a construct like:

new Thread(this.timer(5)).start();

Unfortunately, Java's semantics do not support this. Instead, to start a thread in Java, your class must implement the Runnable interface. (A similar option, which in the interest of clarity we will not discuss, is to extend the Thread class.) You can then pass an instance of your Runnable class to the Thread constructor. Calling the resulting Thread object's start() method will cause the run() method of your object to be invoked in its own execution thread. The Runnable interface does not allow the run() method to return a result, take any parameters or throw any exceptions. The run() method must also be public to satisfy the Runnable interface. With these semantics in mind, the AppTimer class of Listing 2 shows a naive solution to the problem of invoking the timer() method in a separate thread and passing it a parameter.

The programming approach (or "design pattern") illustrated by Listing 2 is simple. When you have a method you wish to execute in a thread, do the following:

  • Change your class definition to include "implements Runnable."
  • Add a public run() method to your class that returns void, takes no parameters and throws no exceptions.
  • At the place in your code where you want to start the method, put its parameters in instance variables, pass the "this" reference to the Thread constructor and call start() on the new Thread.
  • Code your run() method to invoke the target method and pass it the instance variables [or move the code from the target method into run()].
This design pattern does get the job done. That is, the AppTimer class in Listing 2 is successful in running timer(periodLength) in its own thread. In spite of this, the approach is unappealing for several reasons. The main drawback is that it forces you to expose a public run() method that is not really intended for use by clients. Someone looking at Listing 2 could easily be confused about how AppTimer's designer expected the class to be used. If someone mistakenly passes an instance of this class to a Thread constructor and starts it, it will function incorrectly. However, that type of usage is normally inferred by implementing Runnable(). There are also other problems with this approach. It is difficult to use in classes that already expose a public run() method for client use, and it is awkward if a class contains several thread driven-methods.

A more elegant AppTimer implementation, using a design pattern that overcomes these shortcomings, is shown in Listing 3. It defines an inner helper class (TimerHelper) that hides the run() method. An inner class is a class that is defined inside a top-level class as a member of the top-level class. As long as the inner class and its constructors are not public, instances cannot be created outside its defining scope and it is not part of the interface of the top-level class.

Using inner classes to manage threads within a class is an attractive alternative to the approach taken in Listing 2. Not only does it relieve the top-level class of having to implement Runnable and expose a spurious and confusing public run() method, but it also imparts considerable flexibility. The helper class associated with any method can be customized to provide a variety of services such as facilities to stop the method, query its progress or retrieve its result.

The design pattern of Listing 3 generalizes as follows:

  • If you have a class, <class>, with a method, <<method>(<signature>)> that you want to run in its own thread of control, create an inner class within <class> named <method>Helper which implements Runnable.
  • Create a constructor for <method>Helper with the signature <method>Helper (<class>, <signature>).
  • Code the constructor to place its parameters in instance variables, construct a Thread passing the "this" reference and call start() on the new Thread Code <method>Helper's run() method to pass the parameters saved by the constructor to the top-level class's <method>.
  • At the place in the code where you wish to start <method>, construct a new <method>Helper(this,<parameters>).
While this approach is a considerable improvement compared to trying to place all of a top-level class's thread management logic in its run() method, it still has some drawbacks. The main problem is that it may lead to top-level classes that are littered with inner helper classes. Since all the helper classes have similar logic, the code can become tedious to develop, read and maintain.

Fortunately, you can use the capabilities provided by Java's Reflection API (introduced in JDK 1.1) to implement a single class that performs all the thread management functions of this design pattern. The Reflection API allows a method name and signature (represented as a String and an array of Class objects) to be passed among methods. Code receiving such information can determine, at runtime, if a particular class has a method with the specified name and signature, and invoke it if so. These facilities are used in the ThreadHelper class shown in Listing 4 and the much simplified AppTimer implementation of Listing 5 to launch a thread into the timer() method.

The ThreadHelper constructor receives a reference to an Object that contains a method to be executed in an independent thread, the name of the target method and an Object array containing the parameters to be sent to the method. By calling the getClass() method on each parameter [getClass() is inherited by all classes from Object], ThreadHelper builds an array of Class objects that identifies the signature of the target method. Calling getDeclaredMethod() on the target object's class and passing the target method's name and signature returns the Method object for the target method. The Method object is passed to ThreadHelper's run() method, along with its parameters, in instance variables. When ThreadHelper passes its "this" reference to the Thread constructor and calls start() on the new Thread, its run() method is started in a new thread of control. The run() method retrieves the target method's Method object and parameter and invokes the target method.

Because ThreadHelper is intended for general use, it should provide more function than the specialized TimerHelper of Listing 3. Specifically, it needs to allow its client to retrieve any result that might be returned by the target method and to allow the client to (optionally) block and await the completion of the method. These capabilities are provided by ThreadHelper's join() method. This join() method is equivalent to the join() function provided in C by the POSIX pthread package. The boolean completed variable is used by the join() function as a synchronization semaphore. If completed has not been set, join() uses the wait() function to block. When the target method returns, run() calls signalCompletion() to set the semaphore and use notifyAll() to wake up any threads that might be waiting in join(). These actions should be performed only in a "synchronized" method, which is why they are not simply performed in run().

It is also possible, of course, to add a variety of useful methods to ThreadHelper. Some functions like isComplete() or kill() are trivial to add. Others like setTimeOut() are more difficult. The ThreadHelper class shown in Listing 4 illustrates the basic concepts involved in implementing a general helper class to encapsulate thread management primitives. This technique can be used to relieve other classes from having to implement Runnable or use inner classes to exploit Java's powerful multithreading capabilities.

ThreadHelper does have one subtle but significant limitation. The parameters passed to methods by way of the ThreadHelper constructor must be classes rather than Java primitive types. That is, the timer() method of Listing 1 must be modified to take an input parameter of type Integer rather than int. This allows the version of AppTimer shown in Listing 5 to pass the input parameter as "new Integer(periodLength)" rather than simply periodLength. This limitation comes from ThreadHelper calling getClass() for each item in the parameter array. Primitive types (like int) don't inherit from Object and therefore do not have a getClass() method.

The ThreadHelper class is more complex than TimerHelper (see Listing 3), and if you rarely use thread-driven logic, then occasionally incorporating it into specialized inner classes may be adequate. However, the Reflection API makes it possible to provide a general solution with only slightly more effort.

About the Authors
Philip Rousselle develops systems and network management software for Tivoli Systems. He is currently designing techniques for efficiently communicating event data (alerts) over large distributed systems. He is also investigating how network topology information can be used to enhance systems management software. He can be reached at [email protected]

Mike McNally is the lead designer of distributed monitoring products for Tivoli Systems in Austin, TX. He can be reached at [email protected]

	

Listing 1.

void timer(int interval) {

  long startTime = System.currentTimeMillis();

    while(true) {
      try {
            Thread.sleep(interval * 1000);
          }
       catch(InterruptedException ex) { }

        System.out.println(
          "The application has been running for " 
          + (int) ((System.currentTimeMillis() 
                  - startTime) / 1000) + " seconds");
      }
}

Listing 2.

public class AppTimer implements Runnable {

  int periodLength;

  public AppTimer(int newPeriodLength) {

    periodLength = newPeriodLength;
    Thread timer = new Thread(this);
    timer.setDaemon(true);
    timer.start();
  }

  public void run() { timer(periodLength); }

  // void timer(int) see Listing One

Listing 3.

public class AppTimer {

  class TimerHelper implements Runnable {

    AppTimer target;
    int      periodLength;

    TimerHelper(AppTimer newTarget, 
                int newPeriodLength) {

      target = newTarget;
      periodLength = newPeriodLength;
      Thread thread = new Thread(this);
      thread.setDaemon(true);
      thread.start();
    }

    public void run() { 

      target.timer(periodLength);
  }

  public AppTimer(int periodLength) {

    TimerHelper helper = 
             new TimerHelper(this, periodLength);
  }

  // void timer(int) see Listing 1
}

Listing 4.

import java.lang.reflect.*;

public class ThreadHelper implements Runnable {

  Object   targetObject;
  Method   targetMethod;
  Object[] parameters;
  Thread   thread;
  Object   result;
  boolean  completed = false;

  public ThreadHelper(Object   newTarget,
                      String   methodName,
                      Object[] newParameters) 
    throws java.lang.NoSuchMethodException {

    targetObject           = newTarget;
    parameters             = newParameters;
    Class[] parameterTypes = 
                   new Class[parameters.length];

    for (int i = 0; i < parameters.length; i++)
        parameterTypes[i] = 
                        parameters[i].getClass();

    targetMethod = targetObject.getClass().
                     getDeclaredMethod(
                     methodName, parameterTypes);

    thread = new Thread(this);
    thread.setDaemon(true);
    thread.start();
  }

  public void run() {

    try {
          result = targetMethod.invoke(
                       targetObject, parameters);
        }
    catch(InvocationTargetException ex) { 
                          System.err.println(ex);
    }
    catch(IllegalAccessException ex) { 
                          System.err.println(ex); 
    }
    signalCompletion(); 
  }

  synchronized void signalCompletion() {

    completed = true;
    notifyAll();
  }

  public synchronized Object join() {

    while (!completed) {
               try
               {
                 wait();
               }
               catch(InterruptedException ex) { }
    }
    return result;
  }
}

Listing 5.

public class AppTimer {

  public AppTimer(int periodLength) {

    Object[] parm = { new Integer(periodLength) };
    try {
        new ThreadHelper(this, "timer", parm);
    }
    catch(java.lang.NoSuchMethodException ex) { 
        System.err.println(ex);
    } 
  }

  // void timer(Integer) see Listing 1
}
  
      
 

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.