All tasks in a thread pool must provide a mechanism for notifying the application if they terminate abnormally. Failure to do so cannot cause resource leaks because the threads in the pool are still recycled, but it makes failure diagnosis extremely difficult or impossible.
The best way to handle exceptions at the application level is to use an exception handler. The handler can perform diagnostic actions, clean up and shut down the Java Virtual Machine, or simply log the details of the failure.
Noncompliant Code Example (Abnormal Task Termination)
This noncompliant code example consists of the
PoolService class that encapsulates a thread pool and a runnable
Task class. The
Task.run() method can throw runtime exceptions, such as
The task fails to notify the application when it terminates unexpectedly as a result of the runtime exception. Moreover, it lacks a recovery mechanism. Consequently, if
Task were to throw a
NullPointerException, the exception would be ignored.
Compliant Solution (
Task-specific recovery or cleanup actions can be performed by overriding the
afterExecute() hook of the
java.util.concurrent.ThreadPoolExecutor class. This hook is called either when a task concludes successfully by executing all statements in its
run() method or when the task halts because of an exception. Some implementations may fail to catch
java.lang.Error (see Bug ID 6450211 for more information [SDN 2008]). When using this approach, substitute the executor service with a custom
ThreadPoolExecutor that overrides the
terminated() hook is called after all the tasks have finished executing and the
Executor has terminated cleanly. This hook can be overridden to release resources acquired by the thread pool, much like a
Compliant Solution (Uncaught Exception Handler)
This compliant solution sets an uncaught exception handler on behalf of the thread pool. A
ThreadFactory argument is passed to the thread pool during construction. The factory is responsible for creating new threads and setting the uncaught exception handler on their behalf. The
Task class is unchanged from the noncompliant code example.
ExecutorService.submit() method can be used (in place of the
execute() method) to submit a task to a thread pool and obtain a
Future object. When the task is submitted via
ExecutorService.submit(), thrown exceptions never reach the uncaught exception handler because the thrown exception is considered to be part of the return status and is consequently wrapped in an
ExecutionException and rethrown by
Future.get() [Goetz 2006a].
Compliant Solution (
This compliant solution invokes the
ExecutorService.submit() method to submit the task so that a
Future object can be obtained. It uses the
Future object to let the task rethrow the exception so that it can be handled locally.
Furthermore, any exception that prevents
doSomething() from obtaining the
Future value can be handled as required.
TPS03-J-EX0: This rule may be violated only when the code for all runnable and callable tasks has been audited to ensure that exceptional conditions are impossible. Nonetheless, it remains good practice to install a task-specific or global exception handler to initiate recovery or log any exceptional conditions.
Failure to provide a mechanism for reporting that tasks in a thread pool failed as a result of an exceptional condition can make it difficult or impossible to diagnose the problem.
| Interface |
Chapter 7.3, "Handling Abnormal Thread Termination"