75.

About This Bug Pattern

The fact that one thread can throw an exception without affecting other threads can make it difficult to determine which thread has actually thrown the exception. Consider the example shown in Listing 18-1, in which a pair of threads communicate via a producer-consumer model.

Listing 18-1: A Simple, Multithreaded Consumer-Producer Program.

start example
 public class Server extends Thread {   Client client;   int counter;   public Server(Client _client) {     this.client = _client;     this.counter = 0;   }   public void run() {     while (counter < 10) {       this.client.queue.addElement(new Integer(counter));       counter++;     }     throw new RuntimeException("counter >= 10");   }   public static void main(String[] args) {     Client c = new Client();     Server s = new Server(c);     c.start();     s.start();   } } class Client extends Thread {   Vector queue;   public Client() {     this.queue = new Vector();   }   public void run() {     while (true) {       if (! (queue.size() == 0)) {         processNextElement();       }     }   }   private void processNextElement() {     Object next = queue.elementAt(0);     queue.removeElementAt(0);     System.out.println(next);   } } 
end example

In a case such as this, the second thread is entirely dependent upon the first for the data it needs to compute. If the first thread crashes (and in Listing 18-1 it is guaranteed to do so), the second thread will inevitably wait for further input that will never come, essentially freezing the program. The second thread has been abandoned, which is why I call this pattern of bug the Orphaned Thread pattern.

The Symptoms

The most common symptom of this bug pattern is the one I mentioned earlier—namely, that the program will appear to freeze. If you see stack traces printed to standard error and to standard out, that's another symptom.

Such stack traces are especially common with GUI applications on Unix-based systems in which the applications are launched from a terminal window. When a GUI application spills a stack trace to the terminal while freezing, strongly suspect an Orphaned Thread. (For more discussion of Orphaned Threads in the context of GUIs, see the section entitled "Orphaned Threads and GUIs" later in this chapter.)

The Cause

Various program threads are stuck waiting for input from a thread that exited after an uncaught exception was thrown.

Cures and Preventions

Once a bug of this pattern has been diagnosed, the obvious cure is to find and fix the underlying error in the crashing thread. Prevention, however, is more difficult. If you can get away with using a single-threaded design, you'll eliminate many headaches. Chances are, however, that if you settled on a multithreaded design in the first place, there was a good reason for that design choice.

One way to aid in the diagnosis of such crashes is to catch the exceptions thrown in the various threads and notify the dependent threads of the problem before exiting. This is what we've done in Listing 18-2.

Listing 18-2: Example in Which the Client Thread Is Notified of an Error

start example
 import java.util.Vector; public class Server2 extends Thread {   Client2 client;   int counter;   public Server2(Client2 _client) {     this.client = _client;     this.counter = 0;   }   public void run() {     try {       while (counter < 10) {         this.client.queue.addElement(new Integer(counter));         counter++;       }       throw new RuntimeException("counter >= 10");     }     catch (Exception e) {       this.client.interruptFlag = true;       throw new RuntimeException(e.toString());     }   }   public static void main(String[] args) {     Client2 c = new Client2();     Server2 s = new Server2(c);     c.start();     s.start();   } } class Client2 extends Thread {   Vector queue;   boolean interruptFlag;   public Client2() {     this.queue = new Vector();     this.interruptFlag = false;   }   public void run() {     while (! interruptFlag) {       if (! (queue.size() == 0)) {         processNextElement();       }     }     // Processes whatever elements remain on the queue before exiting.     while (! (queue.size() == 0)) {       processNextElement();     }     System.out.flush();   }   private void processNextElement() {     Object next = queue.elementAt(0);     queue.removeElementAt(0);     System.out.println(next);   } } 
end example

If the exceptional circumstance indicates a bug in the program, another option for handling the thrown exception would be to put up a short message notifying the user of the problem and then call System.exit. This option is preferable to allowing the program to freeze, since a user can never really be sure that a program is frozen. He may decide that if he waits just a little longer, it'll come back. But an explicit message indicating a catastrophic error is unmistakable.

This option particularly makes sense when the crash occurs in the program's main thread and the other threads don't manage any critical resources. In other cases, though, it can be dangerous. For example, what if one of the other threads were accessing a shared database? In that case, simply exiting the program could prevent that thread from freeing a lock on the database. Even in the simple example above, calling System.exit in the server thread would cause the client to exit without processing any remaining elements on its queue.

In fact, problems such as these were what spurred Sun to deprecate the stop() method on threads. That method allowed the termination of a thread without its consent. Because it may leave resources in an inconsistent state, the stop() method violated the security model of the language.

Tip 

Sun deprecated the stop() method on threads because terminating a thread without its consent leaves resources in an inconsistent state, thereby violating Java's security model.

In addition to freeing shared resources, we'd also like to offer the user the opportunity to save unsaved data and perhaps file a bug report (as is common in many newer applications, especially on Windows XP). Ideally, tasks like saving files, filing bug reports, and unlocking shared resources could all be done in the exception handling code before calling System.exit.

But remember, we are assuming that there is a bug in the program—the clean up code might trip over the same bug! That would result in a recursive cascade of error messages to the user, which is clearly not what we want. On the other hand, perhaps most users would prefer to risk this cascade of messages in exchange for the chance to save their data.



Bug Patterns in Java
Bug Patterns In Java
ISBN: 1590590619
EAN: 2147483647
Year: N/A
Pages: 95
Authors: Eric Allen

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net