Forward Computing and Control Pty. Ltd.
Threads Package V1.0.3

2005/04/11

au.com.forward.threads
Class ThreadReturn

java.lang.Object
  extended by au.com.forward.threads.ThreadReturn

public class ThreadReturn
extends java.lang.Object

This is the main class of this package and contains the static methods for handling threads.

This class contains a number of static methods that allow you to

Copyright

You may not re-distribute the source code in any form. 

Provided you add the following acknowledgement

    This program incorporates code provided by
    Forward Computing and Control Pty. Ltd.,
    www.forward.com.au
you may re-distribute the compiled java .class file in both private and commercial products.

Guidelines for writing the run() method

The effectiveness of this class depends on the use of an outer try/catch block in the thread's run() method and the addition of ThreadReturn.save(Object) and ThreadReturn.ifInterruptedStop() statements. 

Here is an example of a Thread class run() method that follows this outline.


public class ExampleThread {

  public void run() {
    // put a try/catch at the outermost level
    try {
      // your thread code goes here
      // do some work ...
      // ...

      // stop this thread if it has been interrupted
      ThreadReturn.ifInterruptedStop();

      // do some more work ...
      // ...

      // stop this thread if it has been interrupted
      ThreadReturn.ifInterruptedStop();

      //  .....

      ThreadReturn.save(result);  // optional if you want to return a result
    } catch (Throwable e) {
      // catch all exceptions, errors etc. thrown by this thread here
      ThreadReturn.save(e);
    }
  }
} 
As well as, this you must insure that all fatal errors that occur in methods called by the run() method throw an exception back up to the run() method to be caught by the
    } catch (Throwable e) {
clause.

Then in the parent thread, you can use the following code to wait until the sub-thread dies and collect the result.


Object threadResult = null;
Thread t1 = new ExampleThread();
t1.start();
try {
  threadResult = ThreadReturn.join(t1);
} catch (InterruptedException ex) {
  // some error running t1, handle it here
  if (ex instanceof ThreadException) {
    // t1 throw a Throwable which is the cause of this ThreadException
  } else {
    // ThreadReturn.join(t1) was interrupted
  }
}
Like the Thread class join() method that ThreadReturn.join(t1) is replacing, ThreadReturn.join(t1) can throw an InterruptedException.  If the thread t1's run() terminated because a Throwable was caught in its catch (Throwable e) clause, then ThreadReturn.join(t1) will throw a ThreadException with that Throwable stored as it cause.  ex.getCause() will retrieve it or you can use StackTrace.toString(ex) to get the whole stacktrace as a String.

The result returned by
    threadResult = ThreadReturn.join(t1);
is the Object saved by the
    ThreadReturn.save(result);
statement at the end of the run() method (just above the
    } catch (Throwable e) {
line.

If you did not put this statement in your run() method ThreadReturn.join(t1); will return null

To wait at most x milliseconds for the sub-thread to time out you can use one of the two versions of ThreadReturn.join that specify a timeout, ThreadReturn.join(Thread, long) or ThreadReturn.join(Thread, long, int) e.g. to wait at most 500 milliseconds


Object threadResult = null;
Thread t1 = new ExampleThread();
t1.start();
try {
  threadResult = ThreadReturn.join(t1, 500);
} catch (InterruptedException ex) {
  // some error running t1, handle it here
  if (ex instanceof ThreadException) {
    // t1 throw a Throwable which is the cause of this ThreadException
  } else if (ex instanceof TimedOutdException) {
    // join timedout before t1 died
  } else {
    // ThreadReturn.join(t1) was interrupted
  }
}
In this case ThreadReturn.join(t1,500) can also throw a TimedOutException to indicate the thread did not finish in the specified time.

To terminate a thread

To terminate a thread early call ThreadReturn.stop(thread);.  If the thread in is not blocked then its interrupted status will be set and the next time it executes a ThreadReturn.ifInterruptedStop() statement an InterruptedException will be thrown causing the run() method to terminate. If the thread is waiting in a wait(), join() or sleep() then an InterruptedException will be thrown. If the thread is blocked in an I/O operation on a java.nio.channels.interruptibleChannel then a ClosedByInterruptException will be thrown. Any of these cases will cause the run() method to terminate.

Using ThreadReturn.stop(thread); instead of thread.interrupt(); has the following advantages.
i)thread.interrupt(); will not set the interrupt flag for threads that have not started yet.
ii)ThreadReturn.stop(thread); will continue to interrupt the thread until it dies. This overcomes problems where a method called by the thread's run() method catches and ignores the first interrupt() call.

All the versions of ThreadReturn.join() call the thread's interrupt() method before they return, even if an exception is being thrown.  This means that if you have written your thread's run() method according to the guidelines above your thread will be terminated if ThreadReturn.join() throws any exception.

ThreadListeners

You can add one or more ThreadListeners to a thread using the ThreadReturn.addListener(Thread, ThreadListener) method. Classes implementing the ThreadListener interface have three methods ThreadListener.threadResult(ThreadEvent), ThreadListener.threadInterrupted(ThreadEvent) and ThreadListener.threadError(ThreadEvent).

If the thread terminates normally, threadResult(ThreadEvent) is called with a ThreadEvent containing the source thread, a type of ThreadEvent.RETURN and the result Object saved by the thread, if any.  If the threadResult(ThreadEvent) method throws any Throwable object, it is passed onto that listener's threadError(ThreadEvent) method.  Errors in one listener's ThreadListener.threadError(ThreadEvent) method do not effect the processing of other listeners registered for this thread.
NOTE:threadResult(ThreadEvent) should not modify the result Object contained in the ThreadEvent because the same result Object will be passed on to the other registered listeners for this thread.

If the thread terminates and has saved an InterruptedException or a ClosedByInterruptException, then threadInterrupted(ThreadEvent) is called with a ThreadEvent containing the source thread, a type of ThreadEvent.INTERRUPTED and the Exception saved by the thread. 

If the threadInterrupted(ThreadEvent) method throws any Throwable object, it is passed onto that listener's threadError(ThreadEvent) method.  Errors in one listener's ThreadListener.threadError(ThreadEvent) method do not effect the processing of other listeners registered for this thread.
NOTE:threadInterrupted(ThreadEvent) should not modify the result Object contained in the ThreadEvent because the same Exception will be passed on to the other registered listeners for this thread.

If the thread terminates and has saved some other type of Throwable or if the system monitor thread is interrupted or there is some other error, then threadError(ThreadEvent) is called with a ThreadEvent containing the source thread, a type of ThreadEvent.ERROR and the Throwable object that was thrown.  If the threadError(ThreadEvent) method throws any Throwable objects, they are caught and ignored.  It is your responsibility to catch and handle ALL Throwable objects thrown by this method.

ThreadListeners can be added (or removed) at any time. 

If a ThreadListener is added before the thread is started, the thread is added to the list which is checked every getCheckDelay() milliseconds.  When the thread has started (or has died), a monitor thread is started and joins the thread waiting for it to finish. When the thread has died, all the registered ThreadListeners are called and removed.

Because a thread can only be run once, each listener is removed when it is called.

If a ThreadListener is added after the thread has started (or after it has died), a monitor thread is started and joins the thread waiting for it to finish. When the thread has died, all the registered ThreadListeners are called and removed.

NOTE:
i) All ThreadListener methods are called on a monitor thread that is neither the thread being listened to nor the thread that added the listener.
ii) If you add a ThreadListener after the thread has died, the monitor thread will call the ThreadListener as soon as it starts and passes the ThreadListener, either the Object the thread returned, or the Throwable that was thrown.

Thread resources

If you don't use the ThreadReturn.addListener(Thread,ThreadListener) method, no additional threads are started by this package.  However when you add a ThreadListener, a monitor thread is started for each thread that has listeners.  This monitor thread calls join(java.lang.Thread) to detect when the thread has died and then calls the ThreadListeners registered for that thread.  The monitor thread then dies.

If you add a ThreadListener to a thread that has not started then a single daemon check thread is started.  Once started this check thread continues to run until the application terminates.  The check thread goes to sleep for the number of milliseconds set by setCheckDelay(long) (default 100mS) and then checks if any of the threads with listeners have started since the last check.  For any that have started, or have started and died, since the last check, the check thread starts a monitor thread to handle that thread's listeners.  It then loops to sleep again.

Debug flag

If you set the ThreadReturn.debug to true, debugging messages will be sent to System.out.

saveImmedeately flag

Set ThreadReturn.saveImmedeately to true to force listeners to be called as soon as Thread.save() is called for the first time without waiting for the thread to die and the check thread to notice it. The listeners will be called in the running thread and then removed so subsequent calls to Thread.save() from this thread will NOT fire listeners. However the final result stored will be that from the last Thread.save(), so only have one Thread.save() at the bottom of the try{} block and one other in the catch(){} block, to ensure consistent results.

Author:
matthew ford

Field Summary
static boolean debug
          Set this flag to true to output debugging messages to System.out
static boolean saveImmedeately
          Set this flag to true to force listeners to be called as soon as Thread.save() is called for the first time without waiting for the thread to die and the check thread to notice it.
 
Method Summary
static void addListener(java.lang.Thread thread, ThreadListener listener)
          Adds a listener for a thread.
static long getCheckDelay()
          Gets the loop delay for the check thread that checks on threads waiting to be started.
static boolean hasDied(java.lang.Thread thread)
          Checks if this thread has died
static void ifInterruptedStop()
          Calls yield() on the current thread and then throws an InterruptedException if the thread's interrupted state is set.
static java.lang.Object join(java.lang.Thread thread)
          Waits for a thread to die and returns the Object it saved.
static java.lang.Object join(java.lang.Thread thread, long milliSecs)
          Waits at most milliSecs for a thread to die and returns the Object it saved.
static java.lang.Object join(java.lang.Thread thread, long milliSecs, int nanoSecs)
          Waits at most milliSecs+nanoSecs for a thread to die and returns the Object it saved.
static void removeListener(java.lang.Thread thread, ThreadListener listener)
          Removes a listener from a thread, if the listener has not already been removed.
static void save(java.lang.Object obj)
          Saves this Object against the current thread.
static void setCheckDelay(long millisecs)
          Sets the loop delay for the check thread that checks on threads waiting to be started.
static void stop(java.lang.Thread thread)
          Sets the stop flag for this thread in the threadStopMap and calls the thread's interrupt() method.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

debug

public static boolean debug
Set this flag to true to output debugging messages to System.out


saveImmedeately

public static boolean saveImmedeately
Set this flag to true to force listeners to be called as soon as Thread.save() is called for the first time without waiting for the thread to die and the check thread to notice it. The listeners will be called in the running thread and then removed so subsequent calls to Thread.save() from this thread will NOT fire listeners. However the final result stored will be that from the last Thread.save(), so only have one Thread.save() at the bottom of the try{} block and one other in the catch(){} block, to ensure consistent results.

Method Detail

setCheckDelay

public static void setCheckDelay(long millisecs)
Sets the loop delay for the check thread that checks on threads waiting to be started.  Default 100mS.

Parameters:
millisecs - the delay, in milliseconds, between checking if threads with listeners have been stared.

getCheckDelay

public static long getCheckDelay()
Gets the loop delay for the check thread that checks on threads waiting to be started.  Default 100mS.

Returns:
the delay, in milliseconds, between checking if threads with listeners have been stared.

hasDied

public static boolean hasDied(java.lang.Thread thread)
Checks if this thread has died

Returns:
true if thread has died

addListener

public static void addListener(java.lang.Thread thread,
                               ThreadListener listener)
Adds a listener for a thread.  This ThreadListener is only called once, when the thread has died, and then discarded, because threads can only be run once. The same listener cannot be added twice for the same thread.

All calls to this listener are made by a separate monitor thread. I.e. the monitor thread is neither the thread being listened to, nor the thread calling this method.

Parameters:
thread - the thread to listen for its death.
listener - the ThreadListener to call when the thread has died.

removeListener

public static void removeListener(java.lang.Thread thread,
                                  ThreadListener listener)
Removes a listener from a thread, if the listener has not already been removed.

Parameters:
thread - the thread to remove the listener from.
listener - the ThreadListener to remove. If the ThreadListener is no longer registered for this thread, this method just returns.

stop

public static void stop(java.lang.Thread thread)
Sets the stop flag for this thread in the threadStopMap and calls the thread's interrupt() method. Then each time the listener thread runs interrupt is called again until the thread dies


ifInterruptedStop

public static void ifInterruptedStop()
                              throws java.lang.InterruptedException
Calls yield() on the current thread and then throws an InterruptedException if the thread's interrupted state is set.  This method does not clear the thread's interrupt state.

Throws:
java.lang.InterruptedException - if current thread's interrupt state is set.

save

public static void save(java.lang.Object obj)
Saves this Object against the current thread. 

If the object saved is not a Throwable, then the object will be returned by the ThreadReturn.join() methods and will be passed in a ThreadEvent to the ThreadListener.threadResult(ThreadEvent) method of ThreadListeners registered for the thread.

If the object is an InterruptedException or a ClosedByInterrupteException, then the ThreadReturn.join() methods will throw a ThreadInterruptedException and the object saved will be passed in a ThreadEvent to the ThreadListener.threadInterrupted(ThreadEvent) method of ThreadListeners registered for the thread.

If the object saved is any other Throwable, then the ThreadReturn.join() methods will throw a ThreadException and the Throwable will be passed in a ThreadEvent to the ThreadListener.threadError(ThreadEvent) method of ThreadListeners registered for the thread.

If this method is called more than once by the same thread, only the last Object saved will be returned.

Parameters:
obj - the Object to be save by the current thread.

join

public static java.lang.Object join(java.lang.Thread thread)
                             throws java.lang.InterruptedException
Waits for a thread to die and returns the Object it saved.  If the Object saved by the thread, using ThreadReturn.save(Object), is a Throwable object, then this method throws a ThreadException, otherwise the Object saved is returned by this method.

If this method is interrupted, an InterruptedException is thrown.

Parameters:
thread - the thread to wait for.
Returns:
the non-Throwable Object saved by the thread, or null if none saved.
Throws:
ThreadInterruptedException - thrown if the exception saved by the thread was InterruptedException or ClosedByInterruptException
ThreadException - thrown if some other Throwable was saved by the thread
java.lang.InterruptedException - thrown if the thread calling this method was interrupted.
java.lang.IllegalThreadStateException - thrown if the thread has already died or has not been started or has been re-started after it has died.

join

public static java.lang.Object join(java.lang.Thread thread,
                                    long milliSecs)
                             throws java.lang.InterruptedException
Waits at most milliSecs for a thread to die and returns the Object it saved.  To wait forever call with milliSecs equal to 0.

If the Object saved by the thread, using ThreadReturn.save(Object), is a Throwable object, then this method throws a ThreadException, otherwise the Object saved is returned by this method.

If this method is interrupted, an InterruptedException is thrown.

If this method times out, a TimedOutException is thrown.

Parameters:
thread - the thread to wait for.
milliSecs - the milliSecs to wait for the thread to die.
Returns:
the non-Throwable Object saved by the thread, or null if none saved.
Throws:
ThreadInterruptedException - thrown if the exception saved by the thread was InterruptedException or ClosedByInterruptException
ThreadException - thrown if some other Throwable was saved by the thread
TimedOutException - thrown if this method timed out.
java.lang.InterruptedException - thrown if the thread calling this method was interrupted.
java.lang.IllegalThreadStateException - thrown if the thread has already died or has not been started or has been re-started after it has died.

join

public static java.lang.Object join(java.lang.Thread thread,
                                    long milliSecs,
                                    int nanoSecs)
                             throws java.lang.InterruptedException
Waits at most milliSecs+nanoSecs for a thread to die and returns the Object it saved.  To wait forever call with milliSecs and nanoSecs equal to 0.

If the Object saved by the thread, using ThreadReturn.save(Object), is a Throwable object, then this method throws a ThreadException, otherwise the Object saved is returned by this method.

If this method is interrupted, an InterruptedException is thrown.

If this method times out, a TimedOutException is thrown.

Parameters:
thread - the thread to wait for.
milliSecs - the milliSecs to wait for the thread to die.
nanoSecs - the additional nanoSecs to wait (in the range 0-999999).
Returns:
the non-Throwable Object saved by the thread, or null if none saved.
Throws:
ThreadInterruptedException - thrown if the exception saved by the thread was InterruptedException or ClosedByInterruptException
ThreadException - thrown if some other Throwable was saved by the thread
TimedOutException - thrown if this method timed out.
java.lang.InterruptedException - thrown if the thread calling this method was interrupted.
java.lang.IllegalThreadStateException - thrown if the thread has already died or has not been started or has been re-started after it has died.

Forward Computing and Control Pty. Ltd.
Threads Package V1.0.3

2005/04/11

Copyright ©2002-5, Forward Computing and Control Pty. Ltd
ACN 003 669 994   NSW Australia,   All Rights Reserved.