如何使用UncaughtExceptionHandler重新启动线程

我们已经知道 Java 中有两种异常。 受检的异常和非受检的异常。 必须在方法的throws子句中指定受检的异常或将其捕获在其中。 无需指定或捕获非受检的异常。 当在Thread对象的run()方法内引发受检异常时,由于run()方法不接受throws子句,因此我们必须相应地对其进行处理。 但是,当在Thread对象的run()方法内引发非受检的异常时,默认行为是在控制台中写入栈跟踪(或将其记录在错误日志文件中)并退出程序。

幸运的是,Java 为我们提供了一种机制来捕获和处理Thread对象中抛出的非受检的异常,从而避免程序结束。 这可以使用UncaughtExceptionHandler完成。

让我们以UncaughtExceptionHandler用法为例。 在此示例中,我们创建了一个线程,该线程尝试解析一些应该为整数的字符串。 我们编写了run()方法,使其在执行过程中抛出“ java.lang.NumberFormatException”。 由于程序不会尝试捕获此异常,因此异常会在 JVM 级别浮动,并且线程被杀死。 这绝对是正常行为,但可能不是您期望的行为。

不使用UncaughtExceptionHandler

在现实生活中的应用程序中,即使几次失败,您也想尝试执行一次多次重要任务。 下面的示例演示了用例,首先不使用UncaughtExceptionHandler; 导致线程在失败后立即死亡。

Task.java

class Task implements Runnable
{
   @Override
   public void run()
   {
      System.out.println(Integer.parseInt("123"));
      System.out.println(Integer.parseInt("234"));
      System.out.println(Integer.parseInt("345"));
      System.out.println(Integer.parseInt("XYZ")); //This will cause NumberFormatException
      System.out.println(Integer.parseInt("456"));
   }
}

DemoThreadExample.java

public class DemoThreadExample
{
   public static void main(String[] args)
   {
      Task task = new Task();
      Thread thread = new Thread(task);
      thread.start();
   }
}

下面是运行线程时得到的输出:

123
234
345
Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "XYZ"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at examples.algorithms.sleepingbarber.Task.run(DemoThreadExample.java:24)
	at java.lang.Thread.run(Unknown Source)

使用UncaughtExceptionHandler之后

让我们添加一个UncaughtExceptionHandler实现,以在运行时捕获任何非受检的异常。

ExceptionHandler.java

class ExceptionHandler implements UncaughtExceptionHandler
{
   public void uncaughtException(Thread t, Throwable e)
   {
      System.out.printf("An exception has been captured\n");
      System.out.printf("Thread: %s\n", t.getId());
      System.out.printf("Exception: %s: %s\n", e.getClass().getName(), e.getMessage());
      System.out.printf("Stack Trace: \n");
      e.printStackTrace(System.out);
      System.out.printf("Thread status: %s\n", t.getState());
      new Thread(new Task()).start();
   }
}

现在,将此异常处理程序添加到线程中。

class Task implements Runnable
{
   @Override
   public void run()
   {
      Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());
      System.out.println(Integer.parseInt("123"));
      System.out.println(Integer.parseInt("234"));
      System.out.println(Integer.parseInt("345"));
      System.out.println(Integer.parseInt("XYZ")); //This will cause NumberFormatException
      System.out.println(Integer.parseInt("456"));
   }
}

现在再次运行上面的示例。 这将连续运行。 在现实生活中,如果该任务能够完成任务,那么它将在不引发任何异常的情况下退出并完成其生命周期。

123
234
345
An exception has been captured
Thread: 1394
Exception: java.lang.NumberFormatException: For input string: "XYZ"
Stack Trace: 
java.lang.NumberFormatException: For input string: "XYZ"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at examples.algorithms.sleepingbarber.Task.run(DemoThreadExample.java:24)
	at java.lang.Thread.run(Unknown Source)
Thread status: RUNNABLE
123
234
345
An exception has been captured
Thread: 1395
Exception: java.lang.NumberFormatException: For input string: "XYZ"
Stack Trace: 
java.lang.NumberFormatException: For input string: "XYZ"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at examples.algorithms.sleepingbarber.Task.run(DemoThreadExample.java:24)
	at java.lang.Thread.run(Unknown Source)
Thread status: RUNNABLE
123
234
345

上面的实现可帮助您以某种方式运行线程,直到执行任务为止。 这也可以通过其他多线程概念来实现。

请注意,UncaughtExceptionHandler也可以用于使日志记录更加健壮,而无需重新启动线程,因为在线程执行失败时,默认日志通常无法提供足够的上下文信息。