Java 1.5 Tiger A Developers Notebook [Electronic resources]

David Flanagan, Brett McLaughlin

نسخه متنی -صفحه : 131/ 94
نمايش فراداده

10.1 Handling Uncaught Exceptions in Threads

Normally a Java thread (represented by any class that extends java.lang.Thread) stops when its run( ) method completes. In an abnormal case, such as when something goes wrong, the thread can terminate by throwing an exception. This exception trickles up the thread's ThreadGroup hierarchy, and if it gets to the root ThreadGroup, the default behavior is to print out the thread's name, exception name, exception message, and exception stack trace.

To get around this behavior (at least in Java 1.4 and earlier), you've got to insert your own code into the ThreadGroup hierarchy, handle the exception, and prevent delegation back to the root ThreadGroup. While this is certainly possible, you'll have to define your own subclass of ThreadGroup, make sure any Threads you create are assigned to that group, and generally do a lot of coding that has very little to do with the task at handactually handling the uncaught exception. Tiger simplifies all this dramatically, and lets you define uncaught exception handling on a per-Thread basis.

10.1.1 How do I do that?

The java.lang.Thread class defines a nested interface in Tiger, called Thread.UncaughtExceptionHandler. You can create your own implementation of this interface and pass it to your target Thread's setUncaughtExceptionHandler( ) method (also new in Tiger). Example 10-1 is a simple Thread that does just this.

Example 10-1. Thread with uncaught exception handler
package com.oreilly.tiger.ch10;
public class BubbleSortThread extends Thread {
private int[] numbers;
public BubbleSortThread(int[] numbers) {
setName("Simple Thread");
setUncaughtExceptionHandler(
new SimpleThreadExceptionHandler( ));
this.numbers = numbers;
}
public void run( ) {
int index = numbers.length;
boolean finished = false;
while (!finished) {
index--;
finished = true;
for (int i=0; i<index; i++) {
// Create error condition
if (numbers[i+1] < 0) {
throw new IllegalArgumentException(
"Cannot pass negative numbers into this thread!");
}
if (numbers[i] > numbers[i+1]) {
// swap
int temp = numbers[i];
numbers[i] = numbers[i+1];
numbers[i+1] = temp;
finished = false;
}
}
}
}
}
class SimpleThreadExceptionHandler implements
Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.err.printf("%s: %s at line %d of %s%n",
t.getName( ),
e.toString( ),
e.getStackTrace( )[0].getLineNumber( ),
e.getStackTrace( )[0].getFileName( ));
}
}  

NOTE

I used a bubble sort for the example, but you should know that sorts like quicksort are much faster (and therefore much cooler).

This is a pretty normal Thread, with the exception of two items. First, there is a rather odd condition thrown into the sorting algorithm: if an int less than zero is in the supplied array, an IllegalArgumentException is tossed out. Second, to handle this (rather odd) case, an implementation of Thread.UncaughtExceptionHandler is defined, which prints out some additional information about the problem, including a line number and filename.

To see this in action, you can use Example 10-2, a simple test program.

Example 10-2. Testing the uncaught exception handler
package com.oreilly.tiger.ch10;
import java.io.IOException;
import java.io.PrintStream;
public class ThreadTester {
private int[] posArray = new int[] {1, 3, 6, 3, 4, 2, 5};
private int[] negArray = new int[] {-2, -8, -3, -9, -10};
public ThreadTester( ) {
}
public void testBubbleSort(PrintStream out) throws IOException {
Thread t1 = new BubbleSortThread(posArray);
t1.start( );
out.println("Testing with postive numbers...");
// Wait for the thread to complete
try {
t1.join( );
printArray(posArray, out);
} catch (InterruptedException ignored) { }
Thread t2 = new BubbleSortThread(negArray);
t2.start( );
out.println("Testing with negative numbers...");
try {
t2.join( );
printArray(negArray, out);
} catch (InterruptedException ignored) { }
}
private void printArray(int[] a, PrintStream out) throws IOException {
for (int n : a) {
out.println(n);
}
out.println( );
}
public static void main(String[] args) {
ThreadTester tester = new ThreadTester( );
try {
tester.testBubbleSort(System.out);
} catch (Exception e) {
e.printStackTrace( );
}
}
}

NOTE

Running "ant runch10" will automate this for you.

If you run this example, which generates an error when negative numbers are supplied in negArray, you'll see the SimpleExceptionHandler in action:

NOTE

While this is a fine example, it's still a bad idea to write to the console, or even System.err, unless you're sure where those errors are going, and that they will be seen by some set of human eyes. A better idea would be to log these errors somewhere useful.

[echo] Running ThreadTester...
[java] Testing with postive numbers...
[java] 1
[java] 2
[java] 3
[java] 3
[java] 4
[java] 5
[java] 6
[java] Testing with negative numbers...
[java] Simple Thread: java.lang.IllegalArgumentException: Cannot pass
negative numbers into this thread! at line 23 of
BubbleSortThread.java
[java] -2
[java] -8
[java] -3
[java] -9
[java] -10

Also, notice that the negative array (negArray) doesn't get sorted. That's because the thread threw an exception and never completed the sort.

10.1.2 What about...

...setting an UncaughtExceptionHandler for all threads? Install a default handler: pass an implementation of Thread.UncaughtExceptionHandler to the Thread.setDefaultUncaughtExceptionHandler( ), which is, of course, a static method:

Thread.setUncaughtExceptionHandler(new MyDefaultHandler( ));

If a Thread has its own handler, that of course overrides the default handler. In fact, here's the exact sequence of checks that the JVM goes through when determining how to handle an uncaught exception:

Check for a thread-specific handler to invoke, and if one exists, invoke it.

Invoke the handler of the containing ThreadGroup.

If the containing ThreadGroup (and its ancestors) have not overridden uncaughtException( ), pass the exception up the ThreadGroup hierarchy, until the root ThreadGroup is reached.

Invoke the default handler, obtained by calling Thread.getDefaultExceptionHandler( ).