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. 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 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.
Four ways for join(long) to exit
There are four ways the myThread.join(5000);
call can exit.
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.
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
.
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.
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.myThread
did not finish as and when it was expected to.
Now let's deal with each of these cases:-
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.
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.
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.
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.
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:-
ThreadListener
is only called once, because a thread can only be run once. The listener is
removed automatically when it is called.
join()
will return immediately and the appropriate listener method will be called using the object that was saved by the thread.
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());
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.) 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. 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
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.
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.
threads\src
sub-directory: ThreadTests.java
: Thread.join(long millis)
: Thread.isAlive()
: Thread.start()
: Thread.stop()
: java.lang.ref
: WeakHashMap
: Collections.synchronizedMap()
: Throwable.initCause()
: BigInteger.isProbablePrime()
: Primes.java
: