Home | pfodApps/pfodDevices | WebStringTemplates | Java/J2EE | Unix | Torches | Superannuation | | About Us
 

Forward Logo (image)      

To Catch a Thread
How to throw exceptions from one thread to another.

by Matthew Ford
©2005-2012 Forward Computing and Control Pty. Ltd. NSW Australia
All rights reserved.

SUMMARY

Java has built in support of multi-threading, but although it is easy to start a thread, it is difficult to stop one and when a thread throws an exception, the exception disappears into thin air. This article will describe how to use the au.com.forward.threads package to throw exceptions from a thread back to the thread that is waiting for it to finish. The package also allows you to return a result from a thread when it finishes and to add ThreadListeners to your thread to be notified of the results. Along the way you will meet WeakReferences and Java V1.4's new exception chaining as well as being introduced to Invariant Classes.

Introduction

Java holds out the promise of accessible multi-threaded programming, but it lets you down once you try and use it. You can start a thread but you don't know how to stop it. Java documents a stop() method but tells you not to use it. Also when the thread throws exceptions they disappear into thin air.

This article will show you how to use the au.com.forward.threads package to throw the thread's exceptions back to the main program thread and how to reliably stop a thread. The package also allows you to return a result from a thread. An alternative means of communication in Java is via listeners where the results are returned to those objects which have registered themselves to be called. The package also supports adding ThreadListeners to a thread.

This article is not an introduction to Java threads nor will it make you an expert in Java threads. These requirements are covered in JavaWorld's 101 Introductory Course on Threads by Jeff Friesen and in Allen Holub's series of JavaWorld articles on the complexities of multi-threaded programming. To see what Sun is proposing to do about thread utilities see JSR 166, which also contains some links to thread utility packages. These were implemented in J2SE 5.0. This article assumes you have a basic knowledge of Java programming, including listeners, and have encountered threads and synchronization.

The jar files of the compiled package and the source code (with the examples) are available for the au.com.forward.threads package. You can also view the javadocs on-line. You will need the J2SE (Java 2 Platform, Standard Edition) 1.4 SDK or higher to compile and run the package. The file ThreadTests.java contains detailed testing code for the package. A utility class, StackTrace, is also included. This utility class returns a stack trace as a string.

Contents:-
The Thread Exception Problem
    Four ways for join(long) to exit
ThreadReturn class
Stopping a Thread
Returning a Result from a Thread
ThreadListeners
Weak References
Exception Chaining
Putting it all together
    TestForPrime
    Primes - Main program
    Primes - ThreadListeners
    Primes - Sample Output
Traps to Watch Out for
    Invariant Classes
    TimeOut
Happy Threading
Resources

The Thread Exception Problem

While writing a multi-threaded Java Api for Aptech Systems, Inc.'s new multi-threaded GAUSS Enterpise Engine, I realised what was most commonly needed was a simple way to start a thread to do some task and then to be able to check that it had finished without an error. The following simple code fragment does not do the trick

  Thread myThread = new MyThread(name);
  myThread.start(); // start processing
  // the main program does other useful things here ...
  ....      
  // now the main program is ready to handle the results of the myThread
  // it waits here until the myThread dies 
  // or until the main program thread is interrupted 
  // or until 5 secs have elapsed
  myThread.join(5000);
  // Ok join() returned but what happened to myThread?
  // Did the myThread run successfully?

The method join(long millis) is a method of the java.lang.Thread class that causes the current thread to wait for at most millis milliseconds for the thread object being joined to die. The problem is that when myThread.join(5000); returns you do not know if the thread completed its task normally, or if it died due to an exception, or if the join() method timed out.

Four ways for join(long) to exit

There are four ways the myThread.join(5000); call can exit.

  1. The myThread.run() method terminates due to an exception being thrown. As a result myThread dies and Java continues to the next statement after the join. Java does not inform you that the thread terminated due to an error.

  2. The main program thread is interrrupted. This throws an InterruptedException on the main program thread. In this case you can assume that myThread has not completed and you should take apporiate action, such as stopping myThread.

  3. The myThread.join(5000); times out. In this case Java just continues to the next statement after the join. Java does not inform you that join(5000) has timed out.

  4. The myThread.run() method terminates normally. As a result myThread dies and Java continues to the next statement after the join. Java does not distingush between this case and case i) or iii), so you have no idea if the thread completed its work, if it terminated due to an error or if join(5000) timed out.

The last case, iv), is the only one in which it makes sense to continue. In the first three cases the most reasonable thing to do would be to throw an exception indicating that myThread did not finish as and when it was expected to.

Now let's deal with each of these cases:-

Case i) Sub-thread ends due to Exception

In order to check if the myThread.run() method threw an exception which killed the thread, we need to wrap all the code in the run() method inside a try/catch block. Then in the catch clause we need to save the exception so it can be retrieved by the main program thread. This is done by setting up a static Hashtable and saving the exception with the myThread object as the key. The following code fragment illustrates this:-

// in class MyThread
public void run() {
  try {
    // all the run() method's code goes here
    ...
  } catch (Throwable t) {
    ThreadReturn.save(t);
  }
}

The implementation details of the hashtable are hidden behind the ThreadReturn.save() static method. Note that the code catches a Throwable not an Exception, as we want to be notified about Error objects as well as Exception objects.

Then back in the main program thread, after myThread.join() has returned we can look up the hashtable using myThread as the key and see if there was an exception. If null is returned the myThread.run() method was not terminated due to an exception. Otherwise if myThread.run() threw an exception, we can re-throw it in the main program thread.

  // in main program thread
  Throwable e = (Throwable) threadReturnsMap.get(myThread);
  if (e != null) {
    throw new ThreadException(myThread.getName(),e);
  }

Case ii) Main program thread is interrupted

If the main program thread is interrupted, then an InterruptedException will be thrown on the main program thread. In this case the main program is no longer waiting for myThread to finish, but we should try and shut the thread down. How this can be done is discussed below in Stopping a Thread.

Case iii) join(long) times out

To check if the join() method timed out we need to check if the myThread is still running after myThread.join(5000); returns.

As a first try you might use myThread.isAlive() but the isAlive() method of the java.lang.Thread class cannot tell the difference between a thread that has not been started and one that has died. Instead we need to check if myThread is attached to a ThreadGroup. Every Thread object is attached to a ThreadGroup when it is created, and removed from the group when it dies.

We also need to distingush between a thread that has been started for the first time and one that has died and has been re-started. The javadocs for Thread.start() say that an IllegalThreadStateException() will be thrown if start() is called on a thread that is already started. However no exception is thrown if the start() method is called on a dead thread. In this case isAlive() will return true even though the run() method is not called. The code needs to check for this possibility.

The following code tests these possible thread states before calling join() and then checks if the thread is still running when join() returns.

if (thread.getThreadGroup() == null) {
  // thread has died
  if (thread.isAlive()) {
    // start() was called again
    throw new IllegalThreadStateException(thread.getName()
      +" was re-started after it had died.");
  } // else has died the first time
} else {
  // thread has not died
  if (!thread.isAlive()) {
    throw new IllegalThreadStateException("Tried to join() "+thread.getName()
      +" before start() was called.");
  }
}  
// wait here for the thread to finish
thread.join(milliSecs,nanoSecs); 
// if an interrupt occurs, let the InterruptedException
// propagate out of this method
if (thread.getThreadGroup()!= null) {
  throw new TimedOutException(" join("+thread.getName()+","+milliSecs+
    (nanoSecs!=0?(","+nanoSecs):"")+") timed out.");
}

Case iv) Sub-thread terminates normally

If myThread.run() terminated normally, then no exception will be stored in the hashtable and the main program thread can continue. However this assumes that the myThread.run() method has been coded to throw exceptions all the way to the top of the run() method if an error occurs. Because in the past it has been so difficult to handle exceptions in threads, you will find a lot of code that just catches the exceptions as they occur and writes a message to System.err and perhaps calls System.exit(). In order to successfully apply this package, you will need to check your thread's code to see that all errors generate an exception that propagates back to the top of the run() method and that the run method is enclosed in a try/catch block as described above.

ThreadReturn class

The ThreadReturn class, in the au.com.forward.threads package, handles all these possibilites in its static join() methods. So by putting a try/catch block around the thread's run() method, calling ThreadResult.save(t); in the catch block and replacing
  
myThread.join(5000);
with
  
ThreadReturn.join(myThread, 5000);
in the main program you can find out if your thread completed successfully, or if some exception was thrown or if it timed out. Like the
join() method it replaces, ThreadReturn.join() only throws exceptions that extend from InterruptedException.

Stopping a Thread

Java has a stop() method in the Thread class, but this method is depreciated. Here is the first paragraph of the documentation for the stop() method:

Deprecated. This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait. For more information, see Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?.

In this extract, Sun suggest two methods of stopping a thread:- using a stop variable and calling interrupt(). The method proposed here is to always use interrupt(), because you need to do this anyway if the thread is waiting. If the thread is not waiting, the thread's interrupted state is set and this can be used as the stop variable.

The following method in the ThreadReturn class checks the current thread's interrupt state and throws an InterruptedException if the thread's interrupt flag is set.

public static void ifInterruptedStop() throws InterruptedException {
  Thread.yield(); // let another thread have some time prehaps to stop this one.
  if (Thread.currentThread().isInterrupted()) {
    throw new InterruptedException();
  }
}

To use this method, insert calls to the ThreadReturn.ifInterruptedStop() method at various points in the myThread.run() method. The myThread.run() now looks like

// in class myThread
public void run() {
  try {
    // all the run() method's code goes inside here
    ...
    // do some work
    ThreadReturn.ifInterruptedStop();
    // do some more work
    ...
  } catch (Throwable t) {
    ThreadReturn.save(t);
  }
}

In the main program thread after myThread.join() returns, we call myThread.interrupt() to stop the sub-thread, if it hasn't already stopped. If myThread is waiting in wait(), join() or sleep() then it will throw an InterruptedException. If the myThread is blocked in an I/O operation on a java.nio.channels.InterruptibleChannel then a ClosedByInterruptException will be thrown. Otherwise the myThread's interrupted status will be set and the next time ifInterruptedStop() is called an InterruptedException will be thrown.

Any of these cases will cause the myThread to throw an exception. Provided the run() method and the methods it calls throws these exceptions back to the top of the run() method, then the thread will stop. In the main program, if we find that the thread saved either an InterruptedException or a ClosedByInterruptException then we throw a ThreadInterruptedException to distinguish this case from other exception errors.

Returning a Result from a Thread

It would often be convenient if the thread could return a result back to the main program thread, rather than use some global variable to pass the result back. This can be accomplished by calling ThreadReturn.save(obj); before the thread's run() terminates. For example

// in class myThread
public void run() {
  try {
    // all the run() method's code goes here
    ...
    // do some work
    ThreadReturn.ifInterruptedStop();
    // do some more work
    ...
    ThreadReturn.save(result);
  } catch (Throwable t) {
    ThreadReturn.save(t);
  }
}

Then in the main program thread, the Object that was saved can be retrieved using the code

  // in main program thread
  Object obj = threadReturnsMap.get(myThread);

This is the same code that is used to retrieve the thread exception. We can check if obj is an instanceof Throwable. If it is, then we assume the thread caught and saved a Throwable and we re-throw it in the main program thread. Otherwise we assume it saved a return Object and we return it from the ThreadReturn.join() method.

ThreadListeners

Now that we can catch thread exceptions and return results via ThreadReturn.join(), adding thread listeners is relatively straight forward. First we define a ThreadEvent which can store the source thread and the result Object or Throwable error. The ThreadEvent class also has a type to distinguish between results, interrupts and errors. Then we define the interface ThreadListener which defines three interface methods, threadResult(ThreadEvent), threadInterrupted(ThreadEvent) and threadError(ThreadEvent). You can then use ThreadReturn.addListener(Thread,ThreadListener) to add an object, which implements the ThreadListener interface, to a thread.

The basic idea behind ThreadReturn.addListener(Thread,ThreadListener) is that it starts up a separate monitor thread that calls thread.join() to wait for the thread to die. The monitor thread then checks the threadReturnsMap.get(thread) to see what, if anything, the thread's run() method saved. If the object returned is either an InterruptedException or a ClosedByInterruptException then the threadInterrupted() methods of the the thread's listeners are called. If the object is any other Throwable then the threadError() methods are called. Otherwise the threadReturn() methods are called (even if the object is null).

There are three subtlies to this:-

  1. each ThreadListener is only called once, because a thread can only be run once. The listener is removed automatically when it is called.

  2. if the thread has already died, join() will return immediately and the appropriate listener method will be called using the object that was saved by the thread.

  3. if the thread has not yet started, the start of the monitor thread has to be delayed until the thread has started. A utility thread, the checkThread, runs in the background checking those threads which have listeners but have not yet started, to see when they start. When the checkThread finds one that has started, it starts a monitor thread.

These monitor threads and the check thread are daemon threads, so their existence does not prevent the Java program from terminating.

The two points left to cover about the code are weak references and exception chaining.

Weak References


If the main program thread is interrupted, or if the
join(5000) times out then myThread will still be running after ThreadReturn.join() returns. In this case interrupting myThread to stop it will cause an Exception to be inserted into the hashtable, at some later time, using myThread as its key. Unless it is removed, this reference to the myThread in the hashtable will prevent Java from garbage collecting the myThread object.

Consistent with hiding the implementation details, we don't want to burden the main program thread with the responsiblity of cleaning up the hashtable. So instead of storing a normal (strong) reference to the myThread in the table we store a WeakReference. When there are no strong references to myThread left in the program, Java will garbage collect the WeakRefernce in the hashtable and reclaim the memory. See the package discription of the java.lang.ref package for a complete description of various types of references.

Java has a WeakHashMap class which automatically stores a WeakReference for its keys, so that class is used in the code. This Map needs to be synchronized because the same key will be accessed by both the main program thread and the sub-thread. To synchronize the WeakHashMap, it is wrapped in a Collections.synchronizedMap() which is provided for just this purpose.

private static Map threadReturnsMap = Collections.synchronizedMap(new WeakHashMap());

Exception Chaining

When the ThreadReturn.join() code comes to re-throw the sub-thread exception on the main program thread, the ThreadException(thread, e) constructor uses Java V1.4's exception chaining to chain the existing exception to a new ThreadException. This gives a stack trace like

au.com.forward.threads.ThreadException: Thread_4
        at au.com.forward.threads.ThreadReturn.join(ThreadReturn.java:655)
        at au.com.forward.threads.ThreadReturn.join(ThreadReturn.java:586)
        at ThreadTests.Tests(ThreadTests.java:240)
        at ThreadTests.main(ThreadTests.java:108)
Caused by: java.io.IOException: MyThread throwing IOException in Thread_4
        at MyThread.run(ThreadTests.java:50)

This stack trace shows not only where the IOException was thrown in myThread but also where it was detected in the main program thread. Many, but not all, of Java's exception classes can chain exceptions. Those that do, have a constructor like

public RuntimeException(Throwable cause);

Because Java's join() method already throws an InterruptedException, this package was designed so that ThreadException extends InterruptedException. This also makes sense in that when the sub-thread throws an exception, the main program thread is interrupted. Unfortunately, because of the limited foresight of the implementer of Java's exception chaining, there is no chaining constructor for InterruptedException. (See Bug 4554473 in the Java Bug database for the implementer's explaination of why you would never want to chain to some exceptions. This bug was resolved in later versions of Java.) To chain to exceptions that don't support the new chaining constructor, you have to use the Throwable.initCause() method to manually set the cause exception.

ThreadException(Thread thread, Throwable t) {
  super(thread.getName());
  initCause(t);
}

The ThreadException constructor has package access because it is only called by this package's code. The TimedOutExcepton, which is thrown when ThreadReturn.join() times out, also extends InterruptedException but it does not need to be chained to and so has the usual constructor.

TimedOutException(String message) {
  super(message);
}

The ThreadInterruptedException, which is thrown when the sub-thread saved either an InterruptedException or a ClosedByInterruptException, extends ThreadException and uses chaining.

Putting it all together

To illustrate the application of the au.com.forward.threads package, I wrote a program to find the next possible prime greater than or equal to a given number. Most of the hard work is done by Java's java.math.BigInteger.isProbablePrime() method. This tests if the BigInteger object is probably a prime to some degree of accuracy (see the isProbablePrime() javadocs for details).

The program will use this method to test the odd numbers greater than or equal to the number specified until isProbablePrime() returns true. Three threads will be used to test three blocks of numbers at a time. When a probable prime is found by one of the threads, the others will be stopped and the probable prime printed out as the result. ThreadListeners are used to monitor each of the threads. If all three threads complete without finding a prime, then another three threads are created to process the next three blocks of numbers and so on until a probable prime is found. The threads are given at most 6 seconds to complete their tasks, otherwise a TimedOutException will be thrown. The complete code is in Primes.java

TestForPrime

First let's look at the thread code that tests each odd number in a range for a possible prime.

class TestForPrime extends Thread {
  /**
   *  Increment by 2 to skip even numbers
   */
  private static BigInteger two = new BigInteger("2");
  
  /**
   *  The number to test
   */
  private BigInteger number;
  
  /**
   *  The certainty level = 2^-certainty
   */
  private int certainty;
  
  /**
   *  How many numbers to test starting from the first one
   */
  private int numberToTest;


  /**
   *  Constructor for TestForPrime 
   *
   * @param  name          the name of this thread 
   * @param  rangeStart    the starting number to test
   * @param  numberToTest  how many numbers this thread will test starting
   *                       from the rangeStart
   * @param  certainty     the certainty parameter 
   */
  TestForPrime(String name, BigInteger rangeStart, int numberToTest,
               int certainty) {
    super(name);
    if (rangeStart.signum() != 1) {
      throw new IllegalArgumentException(rangeStart.toString() 
                + " is not positive.");
    }
    if (rangeStart.mod(two).equals(BigInteger.ZERO)) {
      // make it odd
      rangeStart = rangeStart.add(BigInteger.ONE);
    }
    number = rangeStart;

    if (numberToTest < 1) {
      throw new IllegalArgumentException("Must test at least one number");
    }
    this.numberToTest = numberToTest;

    if (certainty < 1) {
      throw new IllegalArgumentException("Certainty argument must be >= 1");
    }
    this.certainty = certainty;
  }


  /**
   *  The TestForPrime run() method 
   *  Loops testing each number in the range for possible primality.
   */
  public void run() {
    try {
      System.out.println("TestForPrime:" + Thread.currentThread().getName() 
          + " " +" starting from " + number.toString());

      for (int i = 0; i < numberToTest; i++) {
        System.out.println("TestForPrime:" + Thread.currentThread().getName() 
                  +" "+new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date())
          +" loop number "+i);
        if (number.isProbablePrime(certainty)) {
          ThreadReturn.save(number);
          return;
        }
        ThreadReturn.ifInterruptedStop();  // have we been stopped
        number = number.add(two); // else try next one
      }
    } catch (Throwable t) {
      ThreadReturn.save(t);
    }
  }
}

The constructor, TestForPrime, names the thread and after checking the inputs are valid saves them in private variables for the thread's run() method to access. The run() method prints out a starting message showing the name of the thread and the starting number of this block and then loops checking for a probable prime. If a probable prime is found it is saved as the result of this thread using ThreadReturn.save(number);. Otherwise the thread checks if it has been asked to stop by calling ThreadReturn.ifInterruptedStop(); and then increments the number by 2 and loops. Only odd numbers are checked.

The entire run() method is enclosed in a try/catch block. If the thread is interrupted, an InterruptedException will be thrown, caught and saved.

Primes - Main program

The main program class Primes implements ThreadListener. The main methods in this class are:-

  /**
   *  The main program 
   *
   * @param  args  The command line arguments.
   *               Could be used to pass in the number and the certainty,
   *               but not implemented here.
   */
  public static void main(String[] args) {
    int certainty = 100;
    String numberString = "1224343333333334549999999999999999999999999555555555555555555333333333333333333333333";
    new Primes().findPrime(numberString, certainty);
  }

  
  /**
   *  find the first probable prime greater than or equal to this number 
   *  If the first three threads don't find one,
   *  create another three threads and try again.
   *
   * @param  numberString  number to start looking from 
   * @param  certainty     the probability that the number found is a prime is 
   *      2^-certainty 
   */
  public void findPrime(String numberString, int certainty) {
    BigInteger prime = null;  // the result
    TestForPrime t_1=null, t_2=null, t_3=null; // the three threads
        
    System.out.println(
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        "  Finding Prime with accuracy of 2^-" + certainty + " starting at ");
    System.out.println(numberString);

    BigInteger number = new BigInteger(numberString);
    
    // have each thread test this many numbers, skipping even numbers
    int increment = 20;
    BigInteger numberIncrement = (new BigInteger("2")).multiply(
                                    new BigInteger("" + increment));
    int counter = 1; // counter for thread names
    
    try {
      while (prime == null) {
        // loop here until possible prime found
        t_1 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
        number = number.add(numberIncrement);
        t_2 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
        number = number.add(numberIncrement);
        t_3 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
  
        // update for next loop
        number = number.add(numberIncrement);
  
        // add listeners
        ThreadReturn.addListener(t_1, this);
        ThreadReturn.addListener(t_2, this);
        ThreadReturn.addListener(t_3, this);
  
        // start all the threads
        t_1.start();
        t_2.start();
        t_3.start();

        // wait at most 6 seconds for the threads to finish
        if( (prime = (BigInteger)ThreadReturn.join(t_1, 2000)) != null) { break;};
        if( (prime = (BigInteger)ThreadReturn.join(t_2, 2000)) != null) { break;};
        if( (prime = (BigInteger)ThreadReturn.join(t_3, 2000)) != null) { break;};
      } // while(prime == null)
        
    } catch (TimedOutException toe) {
      System.out.println(" Did not finish in time: " + StackTrace.toString(toe));
    } catch (ThreadInterruptedException tiex) {
      System.out.println("Caught ThreadInterruptedException in findPrime() "
                          +StackTrace.toString(tiex));
    } catch (ThreadException tex) {
      System.out.println("Caught ThreadException in findPrime() "
                          +StackTrace.toString(tex));
    } catch (InterruptedException iex) {
      System.out.println("Caught InterruptedException in findPrime() "
                          +StackTrace.toString(iex));
    } finally {
      // stop all the threads if any one thread finds a probable prime
      // or throws an exception
      t_1.interrupt();
      t_2.interrupt();
      t_3.interrupt();
    }
      
    if (prime != null) {
     System.out.println("findPrime() returns at " + 
         new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + nl +
          "  with prime:" + prime.toString() + nl);
    }      

  }

The main() method just sets the starting number and the certainity level and creates a new Primes object and calls findPrime(). The findPrime() method sets up the block increment and then loops while a prime has not been found. In each loop it creates three new threads, t_1,t_2 and t_3 to search the next three blocks of numbers. Each thread is given a unique name using a counter.

This Primes object is then added as a listener to each of these three threads before starting them. ThreadReturn.join() is then called on each thread with a timeout of 2 seconds. The Object returned by these ThreadReturn.join() methods is assigned to the prime variable. The first non-null object returned breaks the loop.

The entire loop is wrapped in a try/catch block which prints any exceptions that occur. The finally clause stops each one of the threads if any one of them finds a prime, or if any one of them throws an exception. This is necessary because the application will not terminate until all the threads have died.

Primes - ThreadListeners

As mentioned above, the threads are monitored by the thread listeners. The Primes object, this is passed to all three threads as the listener. The ThreadListener interface methods implemented by Primes are:-

  /**
   *  Result listener.
   *  This method is called when the thread finishes normally. 
   *
   * @param  e  ThreadEvent 
   */
  public synchronized void threadResult(ThreadEvent e) {
    Object obj = e.getObject();
    if (obj == null) {
        // thread did not find a prime
      System.out.println(nl+
          new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
          " Thread:" + e.getThread().getName() + " did not find probable prime: " + nl); 
    } else {
      System.out.println(nl+
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        " Thread:" + e.getThread().getName() + " found prime: " + nl + 
        "  " + ((BigInteger) e.getObject()).toString() + nl);
    }
  }

  /**
   *  Interrupted listener.
   *  This method is called when the thread is stopped due to interrupt
   *
   * @param  e  ThreadEvent 
   */
  public synchronized void threadInterrupted(ThreadEvent e) {
    System.out.println(
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        " in threadInterrupted with ThreadEvent:" + nl + e.toString());
  }

  /**
   *  Error listener.
   *  This method is called when the thread is stopped due to other exceptions
   *
   * @param  e  ThreadEvent 
   */
  public synchronized void threadError(ThreadEvent e) {
    System.out.println(
        new SimpleDateFormat("yy/MM/dd HH:mm:ss.SS").format(new Date()) + 
        " in threadError with ThreadEvent:" + nl + e.toString());
  }

The threadResult(ThreadEvent) method is called each time one of the threads completes normally. If the thread did not find a probable prime then the no result will have been saved and getObject() will return null. On the other hand if a probable prime was found then getObject() will return the probable prime saved by the thread.

This threadResult(ThreadEvent) is synchronized because the same Primes object is passed as the listener to all the three threads. This means multiple threads could be trying to call this threadResult method at the same time. The synchronized keyword prevents more than one thread from accessing any synchronized method in this object. So only one thread at a time is allowed to report its results.

The threadInterrupted(ThreadEvent) method is called if the thread throws an InterruptedException or a ClosedByInterruptException. This will happen if the threads are stopped by calling interrupt(). Again this method is synchronized because more than one thread may be trying to access it at the same time.

The threadError(ThreadEvent) method is called if the thread throws some other exception. Again this method is synchronized.

Primes - Sample Output

Here is some sample output from a run of this program:-

02/05/22 19:43:13.400  Finding Prime with accuracy of 2^-100 starting at 
1224343333333334549999999999999999999999999555555555555555555333333333333333333333333
TestForPrime:Thread_1  starting from 1224343333333334549999999999999999999999999555555555555555555333333333333333333333333
TestForPrime:Thread_1 02/05/22 19:43:13.450 loop number 0
TestForPrime:Thread_2  starting from 1224343333333334549999999999999999999999999555555555555555555333333333333333333333373
TestForPrime:Thread_2 02/05/22 19:43:13.480 loop number 0
TestForPrime:Thread_3  starting from 1224343333333334549999999999999999999999999555555555555555555333333333333333333333413
TestForPrime:Thread_3 02/05/22 19:43:13.510 loop number 0
TestForPrime:Thread_2 02/05/22 19:43:13.520 loop number 1
...
TestForPrime:Thread_2 02/05/22 19:43:14.261 loop number 19

02/05/22 19:43:14.271 Thread:Thread_3 did not find probable prime: 

02/05/22 19:43:14.271 Thread:Thread_1 did not find probable prime: 

02/05/22 19:43:14.281 Thread:Thread_2 did not find probable prime: 

TestForPrime:Thread_4  starting from 1224343333333334549999999999999999999999999555555555555555555333333333333333333333453
TestForPrime:Thread_4 02/05/22 19:43:14.291 loop number 0
TestForPrime:Thread_5  starting from 1224343333333334549999999999999999999999999555555555555555555333333333333333333333493
TestForPrime:Thread_5 02/05/22 19:43:14.301 loop number 0
TestForPrime:Thread_6  starting from 1224343333333334549999999999999999999999999555555555555555555333333333333333333333533
TestForPrime:Thread_6 02/05/22 19:43:14.311 loop number 0
TestForPrime:Thread_4 02/05/22 19:43:14.321 loop number 1
...
TestForPrime:Thread_6 02/05/22 19:43:14.872 loop number 11
findPrime() returns at 02/05/22 19:43:14.922
  with prime:1224343333333334549999999999999999999999999555555555555555555333333333333333333333459

02/05/22 19:43:14.922 Thread:Thread_4 found prime: 
  1224343333333334549999999999999999999999999555555555555555555333333333333333333333459

02/05/22 19:43:14.922 in threadInterrupted with ThreadEvent:
ThreadEvent_source:Thread_6 ThreadEvent_type:INTERRUPTED Stored_exception:java.lang.InterruptedException
        at au.com.forward.threads.ThreadReturn.ifInterruptedStop(ThreadReturn.java:509)
        at TestForPrime.run(Primes.java:88)

02/05/22 19:43:14.962 Thread:Thread_5 found prime: 
  1224343333333334549999999999999999999999999555555555555555555333333333333333333333497

From the output you can see the program goes around the loop twice before it finds a probable prime and then it finds two. Thread_4 finds one and calls the threadResult() method of the ThreadListener which prints out the probable prime. In the main program thread the statement
if( (prime = (BigInteger)ThreadReturn.join(t_1, 2000)) != null) { break;};
returns the prime from Thread_4 and saves it in
prime and then breaks out of the loop. The finally clause stops all three threads. However before Thread_5 can be stopped, it also finds a prime and calls the threadResult() method also. On the other hand Thread_6 is interrupted before it finishes and calls the threadInterrupted method of the ThreadListener.

Traps to Watch Out for

The most obvious problem in multi-threaded programming is determining the need for synchronization. This topic is covered in all articles on Java threads (see the Resources below), so it will not be covered again here. Instead I will discuss Invariant Classes and the timeout setting.

Invariant Classes

In the main program, the three threads are initialized by

        t_1 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
        number = number.add(numberIncrement);
        t_2 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
        number = number.add(numberIncrement);
        t_3 = new TestForPrime("Thread_"+counter++, number, increment, certainty);
  
        // update for next loop
        number = number.add(numberIncrement);

The important thing to note here is that after t_1 has been passed a reference to number, the next statement is

        number = number.add(numberIncrement);

The question is, does the number.add() method modify the number object that has already been passed to the constructor for t_1? If it does then the starting number for t_1 will be changed and all three threads will start at the same place. It happens that in the BigInteger class the add() method creates a new BigInteger object which is returned, rather than updating the existing object. So this code works. However this is not true for all Java classes. For example the add() method of the Vector class does not create a new vector but modifies the existing vector. This means that if we were passing Vector objects to the threads instead of BigInteger objects the code shown above would not work.

I call classes that cannot modify their contents, Invariant Classes. Invariant Classes are particularly useful for multi-threaded programs, because they cannot be changed after they have been created. You do not have to worry about synchronization or about the order in which threads may access Invariant Class objects. Invariant Classes are particularly useful for returning result objects, which may be passed to multiple listeners. Using an Invariant Class ensures none of the listeners can change the result that is passed to the next listener in the list.

Some of the Invariant Classes in Java are:-
String, Boolean, Float, Integer, Character, Double and BigInteger.

The general rule for writing Invariant Classes is the there are no methods that can change the class' variables and all their variables have private access and are either other Invariant Classes or Java primitives.

The Java primitives are:- int, short, long, byte, float, double, char and boolean.

Java primitives are also safe to pass between threads as a copy is made each time a primitive is passed as an argument to a method.

Invariant Classes and Java primitives are "thread protected". This means you can safely give multiple threads a local reference to these objects and primitives, and be sure nothing the thread does will change the contents retrieved in another thread which also has a reference to the same object or primitive. This is not the same as "thread safe". Thread safe variables and objects are synchronized so that multiple threads can access and update them reliably.

TimeOut

In the above code the timeout for each join() is set at 2 seconds. This means that the first thread has 2 seconds to complete its task, then the second thread has a further 2 seconds to complete its task. A total of 4 seconds. Finally the third thread has another 2 seconds to complete its task, giving it a total of 6 seconds. Since all three threads are doing similar tasks, it would be preferable for them to all have the same timeout setting.

A utility class, TimeOut has been provided in the au.com.forward.threads package to handle this problem. To use it you modify the code to:-

        // start all the threads
        t_1.start();
        t_2.start();
        t_3.start();

        // wait at most 2 seconds for the threads to finish
        TimeOut timeOut = new TimeOut(2000);
        if( (prime=(BigInteger)ThreadReturn.join(t_1, timeOut.timeRemaining()))!=null){break;};
        if( (prime=(BigInteger)ThreadReturn.join(t_2, timeOut.timeRemaining()))!=null){break;};
        if( (prime=(BigInteger)ThreadReturn.join(t_3, timeOut.timeRemaining()))!=null){break;};

The new TimeOut(2000) sets an absolute time out of now plus 2 seconds. Then each time timeRemaining() is called, the number of milliseconds left before the timeout expires is returned. This ensures that each thread will have at most 2 seconds to complete its task. If the time out has expired then timeRemaining() returns 1, because a negative number is not valid for join() and a return of 0 would cause the join() to wait forever. This results in at most an extra millisecond of time for each thread after the first one.

Happy Threading

So by adding a try/catch block at the top level of your thread's run() method, you can use ThreadReturn.save(Throwable e) in the catch clause to save any exception your thread throws. Then in the main program thread, you can use ThreadReturn.join(Thread thread) to wait for your thread to finish and check if it threw any exceptions. If your thread did throw an exception, then ThreadReturn.join(Thread thread) will re-throw it in the main program thread using Java 1.4's exception chaining. This lets you clearly see both where the exception occurred in your thread and where it was detected in the main program. You can also return a result object from your thread to the waiting thread.

You can optionally specify a maximum time to wait for the thread to finish. If ThreadReturn.join(Thread thread) times out, a TimedOutException is thrown on the main program thread. Finally by inserting the ThreadReturn.ifInterruptedStop(); statement at various points in your thread's run() method, you can stop the thread by calling the its interrupt() method.

Resources

Biography

Matthew Ford has been programming in Java since its first beta version was released. He has extensive experience in multi-threaded client/server applications. He has written the multi-threaded Java API for Aptech Systems, Inc.'s GAUSS Enterprise Engine which provides concurrent execution of the GAUSS matrix programming language in multiple workspaces. This tip is a result of solving the problem of how to return errors from the separate threads to the main Java program.


Forward home page link (image)

Contact Forward Computing and Control by
©Copyright 1996-2020 Forward Computing and Control Pty. Ltd. ACN 003 669 994