前言

本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

本专栏目录结构和参考文献请见100个问题搞定Java并发

正文

UncaughtExceptionHandler

在 Thread API 中提供了 UncaughtExceptionHandler ,它能检测出某个线程由于未捕获的异常而终结的情况,能有效地防止线程泄漏问题当一个线程由于未捕获异常而退出时, JVM 会把这个事件报告给应用程序提供的 UncaughtExceptionHandler 异常处理器。

如果没有提供任何异常处理器,那么默认的行为是将梭追踪信息输出到 System.err 。

UncaughtExceptionHandler 源码(JDK8)

/**
 * 当线程由于未捕获异常而突然终止时调用的处理程序接口。 
 * 当线程由于未捕获异常即将终止时,Java虚拟机将使用getUncaughtExceptionHandler查询线程的
 * UncaughtExceptionHandler,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。
 * 如果线程未显式设置其UncaughtExceptionHandler,则其ThreadGroup对象将充当其
 * UncaughtExceptionHandler。
 * 如果ThreadGroup对象对处理异常没有特殊要求,它可以将调用转发给默认的未捕获异常处理程序。
 */
public interface UncaughtExceptionHandler {
    /**
     * 方法,该方法在给定线程由于给定的未捕获异常而终止时调用。 
     * Java虚拟机将忽略此方法引发的任何异常。 
     * 参数: 
     * t–线程 
     * e–异常
     */
    void uncaughtException(Thread t, Throwable e);
 }

异常处理器如何处理未捕获异常,取决于对服务质量的需求。

最常见的响应方式是将个错误信息以及相应的栈追踪信息写入应用程序日志中。

异常处理器还可以采取更直接的响应,例如尝试重新启动线程,关闭应用程序,或者执行其他修复或诊断等操作。

Guava

下面这段代码来自 Guava(23.0) 包,大家可以参考:

package com.google.common.util.concurrent;

import static java.util.logging.Level.SEVERE;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Locale;
import java.util.logging.Logger;

/**
 * Factories for {@link UncaughtExceptionHandler} instances.
 *
 * @author Gregory Kick
 * @since 8.0
 */
@GwtIncompatible
public final class UncaughtExceptionHandlers {
  private UncaughtExceptionHandlers() {}

  /**
   * Returns an exception handler that exits the system. This is particularly useful for the main
   * thread, which may start up other, non-daemon threads, but fail to fully initialize the
   * application successfully.
   *
   * <p>Example usage:
   *
   * <pre>
   * public static void main(String[] args) {
   *   Thread.currentThread().setUncaughtExceptionHandler(UncaughtExceptionHandlers.systemExit());
   *   ...
   * </pre>
   *
   * <p>The returned handler logs any exception at severity {@code SEVERE} and then shuts down the
   * process with an exit status of 1, indicating abnormal termination.
   */
  public static UncaughtExceptionHandler systemExit() {
    return new Exiter(Runtime.getRuntime());
  }

  @VisibleForTesting
  static final class Exiter implements UncaughtExceptionHandler {
    private static final Logger logger = Logger.getLogger(Exiter.class.getName());

    private final Runtime runtime;

    Exiter(Runtime runtime) {
      this.runtime = runtime;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
      try {
        // cannot use FormattingLogger due to a dependency loop
        logger.log(
            SEVERE, String.format(Locale.ROOT, "Caught an exception in %s.  Shutting down.", t), e);
      } catch (Throwable errorInLogging) {
        // If logging fails, e.g. due to missing memory, at least try to log the
        // message and the cause for the failed logging.
        System.err.println(e.getMessage());
        System.err.println(errorInLogging.getMessage());
      } finally {
        runtime.exit(1);
      }
    }
  }
}

总结

在运行时间较长的应用程序中,通常会为所有线程的未捕获异常指定同一个异常处理器,并且该处理器至少会将异常信息记录到日志中。

要为线程池中的所有线程设置一个 UncaughtExceptionHandler ,需要为 ThreadPoolExecutor 的构造函数提供一个 ThreadFactory 。 (与所有的线程操控一样,只有线程的所有者能够改变线程的 UncaughtExceptionHandler )

标准线程池允许当发生未捕获异常时结束线程,但由于使用了一个 try – finally 代码块来接收通知,因此当线程结東时,将有新的线程来代替它。

如果没有提供捕获异常处理器或者其他的故障通知机制,那么任务会悄悄失败,从而导致极大的混乱。

如果你希望在任务由于发生异常而失败时获得通知,并且执行一些特定于任务的恢复操作,那么可以将任务封装在能捕获异常的 Runnable 或 Callable 中,或者改写 ThreadPoolExecutor 的 afterExecute 方法。

令人困惑的是,只有通过 execute 提交的任务,才能将它抛出的异常交给未捕获异常处理器,而通过 submit 提交的任务,无论是抛出的未检査异常还是已检査异常,都将被认为是任务返回状态的一部分。

如果一个由 submit 提交的任务由于抛出了异常而结束,那么这个异常将被 Future.get 封装在 ExecutionException 中重新抛出。

上一篇 下一篇