The ThreadDeath Class

The ThreadDeath class is a special Throwable class that is used to stop a thread. This class extends the Error class and hence should not be caught by the program. In theory, there is no reason to catch and handle any Throwable object that is not an object of the Exception class, and that usually applies to the ThreadDeath class as well.

How does throwing an object actually stop a thread? As we mentioned, the thread cleans up after itself when the run() method completes. Of course, there are two ways for the run() method to complete: it can complete on its own by simply returning, or it can throw or fail to catch an exception (including an Error or Throwable object).

By default, if the run() method throws an exception, the thread prints an error message, along with the stack trace of the exception. However, a special case is made for the ThreadDeath object. If a ThreadDeath object is thrown from the run() method, the uncaughtException() method simply returns.

The ThreadDeath object is normally used only in conjunction with the stop() method. When you call the stop() method on a particular thread, a ThreadDeath object is created and then thrown by the target thread. Since the stop() method is deprecated, the utility of this technique is minimal.

Is it possible to catch the ThreadDeath object? It is possible to catch any Throwable object; however, it is not advisable to use this technique to prevent the death of the thread. After all, if we did not want the thread to die, why was the stop() method called? And what about other threads that expect the target thread to stop? The thread that has called the target thread’s stop() method might then attempt to join the target thread; if you catch ThreadDeath, the join will never complete.

One possible use of this technique is to handle cleanup conditions when the thread is being stopped. In this case, we would catch the ThreadDeath object, execute the cleanup code, and then rethrow the object. However, even in this case it is hard to justify catching the ThreadDeath object; we could accomplish the same thing by using the finally clause. The finally clause is always executed, though, and you may conceivably only want the code to be executed if the thread is stopped.

It’s interesting to note that the ThreadDeath class is what caused the stop() method to become deprecated in the first place: if the exception is thrown in the middle of a synchronized method or block, the thread will immediately return from that method, (possibly) leaving the critical data of the object in an inconsistent state. You could judiciously catch the ThreadDeath exception and clean up your code correctly to make the stop() method safer, but that will only protect your own code, not the code in critical sections of the virtual machine or the code within the Java API itself.

However, the ThreadDeath class may be useful in one limited circumstance as a replacement for the stop() method. Say that a thread encounters an error and wants to terminate itself, but the error is not egregious enough that it wants the user to see the error. One way to do this is for the thread simply to return from its run() method, but it may be difficult for the thread to unwind all of its methods in order to do that. A second way is for the thread to call the stop() method on itself. And a final way is for the thread to throw a ThreadDeath error. This will unwind the thread’s stack and cause the thread to exit its run() method, but since the ThreadDeath error is handled by the virtual machine in the special manner, the end user will be unaware that the thread has exited: there will be no stack trace printed to the Java console.

Even so, a thread that wants to terminate itself cannot simply throw a ThreadDeath object willy-nilly: the thread must throw this object only when it is sure that it has not left any data in a possibly inconsistent state (e.g., when it is not presently holding any locks). If you’ve programmed your thread very carefully and are sure that the thread has left all data in a consistent state, it is safe to throw the ThreadDeath object to make your thread exit immediately. This is really the same thing as calling the stop() method on yourself: the only difference is that the compiler will complain if you call the stop() method (even if a thread calls it on itself when it knows it is safe to do so), whereas the compiler won’t complain about throwing a ThreadDeath object. Still, you have to be very careful only to do this when it’s absolutely safe to do so.

Inheriting from the ThreadDeath Class

The ThreadDeath object is used in conjunction with a new stop() method:

void stop(Throwable o) (deprecated in Java 2)

Terminates an already running thread. The thread is stopped by throwing the specified object.

The stop() method is overloaded with a signature that allows the developer to unwind the stack with any Throwable object. Until now, there was little reason to stop the thread with any object but a ThreadDeath object. But we can now override the default exception handler; if we wanted a thread to die due to a particular reason and handle the special reason, we might create a new Throwable type and handler as follows:

public class ATMThreadDeath extends ThreadDeath {
    public int reason;
    public ATMThreadDeath(int reason) {
        this.reason = reason;
    }
}
    
public class ATMThreadGroup extends ThreadGroup {
    public ATMThreadGroup(String name) {
        super(name);
    }

    public void uncaughtException(Thread t, Throwable e) {
        if (e instanceof ATMThreadDeath) {
            HandleSpecialExit(e);
        }
        super.uncaughtException(t, e);
    }
}

Assuming that there are special exit-handling conditions that need to be taken care of, we can create a new version of the ThreadDeath class that contains the reason for the death. Given this new version of the ThreadDeath class, we can then create a special handler to take care of the exit conditions. Of course, we must now use the other stop() method to send our ATMThreadDeath object:

runner.stop(new ATMThreadDeath(3));

Can we use the stop() method to deliver a generic exception to another thread? It will work, but it is not advisable. There are many reasons against doing so. Depending on the exception and when the stop() method is called, we might throw an exception that violates the semantics of the throws keyword. The compiler requires that you handle exceptions it knows will be thrown, but the compiler will not, in this case, know about the generic exception you are causing the other thread to throw. If you execute the code:

runner.stop(new IOException());

the runner thread may be executing code that is not prepared to handle an IOException. This is confusing at best.

We could list more reasons against using this technique, but that will not stop certain developers from using this technique as a signal delivery system.[15] Simply put, stop() was not designed as a signal delivery system, and using it as such may yield unexpected or platform-specific results.

More on Thread Destruction

By calling the stop() method and using the exception mechanism to exit the run() method, we caused the run() method to exit prematurely and, hence, allowed the thread to terminate. We could also have killed the thread using the destroy() method, which, in turn, terminates the execution of the run() method. The difference is the way the run() method exits: the first case allows the run() method to terminate, and hence kills the thread. The second mechanism kills the threads, which terminates the run() method.

By allowing the run() method to terminate, the stack for the thread is allowed to unwind. This means that the finally clauses are all allowed to execute as the stack is unwound. This allows a better state to exist in the program when the thread terminates; it also allows synchronization locks to be released as the stack is unwound. Because of these benefits, the thread is always allowed to unwind rather than just to terminate. Of course, the problem is that since the thread death exception may be thrown at any time, there may not be a finally clause to execute, which again leads us to the problem that requires the stop() method to be deprecated.

In order to be complete in our discussion, we’ll now examine the destroy() method, which allows the thread to be destroyed without unwinding the stack. This method would be used as a last resort:

void destroy() (not implemented)

Destroys a thread immediately. This method of the Thread class does not perform any cleanup code, and any locks that are locked prior to this method call will remain locked.

Why would you want to not clean up after a thread? There should be no case where you do not want to clean up after a thread. However, there may be cases where the cleanup code may not work. For example, with the wait and notify mechanism, it may not be possible to immediately unwind the stack due to an unavailable lock: a thread that is stopped while it is executing the wait() method may not terminate for quite a while. If the thread deadlocks while trying to reacquire the lock, then the thread will never exit. A waiting period to unwind may not be acceptable.

However, we should regard this as a bug in the program and fix the code rather than leave possibly unreleased locks. As it stands now, it doesn’t really matter: the destroy() method is not actually implemented in the reference JDK and simply throws a NoSuchMethodError.



[15] Or from using the exception system as a callback mechanism.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset