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.
The ThreadDeath object is used in
conjunction with a new stop()
method:
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.
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:
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.