Home
| pfodApps/pfodDevices
| WebStringTemplates
| Java/J2EE
| Unix
| Torches
| Superannuation
|
| About
Us
|
To Catch a Thread
|
by Matthew Ford
©2005-2012 Forward Computing and Control
Pty. Ltd. NSW Australia
All rights reserved.
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.
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
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.
There are four ways the myThread.join(5000);
call can exit.
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.
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
.
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.
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:-
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); }
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.
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."); }
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.
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
.
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.
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.
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:-
each
ThreadListener
is only called once, because a thread can only be
run once. The listener is removed automatically when it is called.
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.
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.
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());
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.
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. ThreadListener
s
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
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.
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.
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
.
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 statementif(
(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
.
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.
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.
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.
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.
The first JavaWorld 101 Java Thread
article by Jeff
Friesen:
http://www.javaworld.com/javaworld/jw-05-2002/jw-0503-java101.html
Allen Holub's last JavaWorld Threading
article containing references to his previous Java Thread
articles:
http://www.javaworld.com/javaworld/jw-06-1999/jw-06-toolbox.html
Sun's JSR 166 proposal for thread
utilities, which also contains some links to thread utility
packages:
http://www.jcp.org/en/jsr/detail?id=166
Aptech Systems Inc.'s GAUSS
multi-threading, multi-workspace maths engine:
www.aptech.com
Java source code, with examples, and the
compiled jar for the au.com.forward.threads package is in the,
threads/src
sub-directory:
src
Javadocs for the au.com.forward.threads
package are here:
javadocs/index.html
Java source code for the testing the
au.com.forward.threads package, ThreadTests.java
:
src/examples/ThreadTests.java
Download the J2SE 1.4 SDK:
http://java.sun.com/j2se/1.4/download.html
Javadoc for Thread.join(long
millis)
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/Thread.html#join(long)
Javadoc for Thread.isAlive()
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/Thread.html#isAlive()
Javadoc for Thread.start()
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/Thread.html#start()
Javadoc for Thread.stop()
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/Thread.html#stop()
Why are Thread.stop, Thread.suspend and
Thread.resume Deprecated?:
http://java.sun.com/j2se/1.4/docs/guide/misc/threadPrimitiveDeprecation.html
"How to avoid traps and correctly
override methods from java.lang.Object". This covers how to
write clone(), toString(), equals(), hashCode() and finalize()
methods.:
http://www.javaworld.com/javaworld/jw-01-1999/jw-01-object_p.html
Javadoc for java.lang.ref
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/ref/package-summary.html
Javadoc for WeakHashMap
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/util/WeakHashMap.html
Javadoc for Collections.synchronizedMap()
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/util/Collections.html#synchronizedMap(java.util.Map)
Introduction to Java V1.4's Chained
Exception Facility:
http://java.sun.com/j2se/1.4/docs/guide/lang/chained-exceptions.html
Bug 4554473 requesting exception chaining
be extended to all exception
classes:
http://developer.java.sun.com/developer/bugParade/bugs/4554473.html
Javadoc for Throwable.initCause()
:
http://java.sun.com/j2se/1.4/docs/api/java/lang/Throwable.html#initCause(java.lang.Throwable)
Javadoc for BigInteger.isProbablePrime()
:
http://java.sun.com/j2se/1.4/docs/api/java/math/BigInteger.html#isProbablePrime(int)
Java source code for the Primes program,
Primes.java
:
src\examples\Primes.java
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.
Contact Forward Computing and Control by
©Copyright 1996-2020 Forward Computing and Control Pty. Ltd.
ACN 003 669 994