[JAVA] Java如何主动从当前线程获取异常信息

2069 0
Honkers 2022-11-8 17:50:38 | 显示全部楼层 |阅读模式
目录

    Java主动从当前线程获取异常信息
      使用场景写法
    Java捕获并处理线程异常:Thread及ThreadPoolExecutor线程池异常捕获
      通过Thread.UncaughtExceptionHandler捕获线程异常ThreadPoolExecutor线程池异常捕获



Java主动从当前线程获取异常信息


使用场景

在单个方法内主动捕获异常,并将异常的错误栈信息以日志的方式打出来

写法

当前方法throw 了 异常,即改方法存在异常的情况,则可以使用如下方式获取当前线程中的异常:
  1. // 主动获取当前线程异常栈信息
  2. StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
  3. // 以日志的方式打出 \n 每一行错误栈信息结束后换行
  4. log.error(StringUtils.join(stackTraceElements, '\n'));
复制代码
Java捕获并处理线程异常:Thread及ThreadPoolExecutor线程池异常捕获


通过Thread.UncaughtExceptionHandler捕获线程异常

Thread.UncaughtExceptionHandler类的作用:捕获并处理线程run方法抛出的异常。
strong>使用示例</strong>
为单个线程设置异常捕获
  1. Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
  2.   System.out.println("报错线程:" + t.getName());
  3.   System.out.println("线程抛出的异常:" + e);
  4. };
  5. Thread thread = new Thread(() -> {
  6.   throw new RuntimeException("throw a new Exception ! ");
  7. });
  8. thread.setUncaughtExceptionHandler(exceptionHandler); // 设置异常处理器
  9. thread.start();
复制代码
如果项目中,全局的Thread线程处理异常的方式都相同,那么可以设置一个全局的异常捕获类。
  1. Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
  2.   System.out.println("报错线程:" + t.getName());
  3.   System.out.println("线程抛出的异常:" + e);
  4. };
  5. Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
复制代码
通过这种方式,后续的Thread都可以公用这个异常处理类。如果需要用其它方式处理异常时,只需要实现1中的内容即可。
部分源码解析
  1. UncaughtExceptionHandler源码
  2. // Thread.UncaughtExceptionHandler
  3. @FunctionalInterface
  4. public interface UncaughtExceptionHandler {
  5.   void uncaughtException(Thread t, Throwable e);
  6. }
复制代码
Thread中的UncaughtExceptionHandler属性
  1. // 作用于当个Thread线程
  2. private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
  3. // static修饰,其可以作用于所有Thread线程
  4. private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
复制代码
在Thread中,会首先提供Thread私有的异常处理类,然后才是全局
  1. public UncaughtExceptionHandler getUncaughtExceptionHandler() {
  2.   return uncaughtExceptionHandler != null ?
  3.     uncaughtExceptionHandler : group;
  4. }
复制代码
实现原理
当线程由于未捕获的异常而即将终止时,Java虚拟机将使用调用线程的getUncaughtExceptionHandler方法,以此来获取UncaughtExceptionHandler类,并执行其对异常的处理方法。
如果一个线程没有显式实现UncaughtExceptionHandler ,那么它的ThreadGroup对象将充当它的UncaughtExceptionHandler类。
  1. // Thread#getUncaughtExceptionHandler
  2. public UncaughtExceptionHandler getUncaughtExceptionHandler() {
  3.   return uncaughtExceptionHandler != null ?
  4.     uncaughtExceptionHandler : group;
  5. }
复制代码
ThreadGroup实现了UncaughtExceptionHandler类。
  1. public class ThreadGroup implements Thread.UncaughtExceptionHandler {
  2. // ……
  3. public void uncaughtException(Thread t, Throwable e) {
  4.   // 父级ThreadGroup的处理方法
  5.    if (parent != null) {
  6.      parent.uncaughtException(t, e);
  7.    } else {
  8.     // Thread 的全局默认处理方法
  9.      Thread.UncaughtExceptionHandler ueh =
  10.        Thread.getDefaultUncaughtExceptionHandler();
  11.      if (ueh != null) {
  12.        ueh.uncaughtException(t, e);
  13.      } else if (!(e instanceof ThreadDeath)) {
  14.        System.err.print("Exception in thread ""
  15.                 + t.getName() + "" ");
  16.        e.printStackTrace(System.err);
  17.      }
  18.    }
  19. }
  20. // ……
  21. }
复制代码
ThreadPoolExecutor线程池异常捕获

使用示例
要捕获ThreadPoolExecutor线程池中的线程执行异常,需要实现被protected修饰的方法afterExecute,在该方法里面处理异常即可。
  1. // ThreadPoolExecutor#afterExecute
  2. class ExtendedExecutor extends ThreadPoolExecutor {
  3. protected void afterExecute(Runnable r, Throwable t) {
  4.   super.afterExecute(r, t);
  5.   // 如果Runnable是Future类型,那么异常将会直接通过Future返回
  6.   if (t == null && r instanceof Future<?>) {
  7.    try {
  8.     Object result = ((Future<?>) r).get();
  9.    } catch (CancellationException ce) {
  10.      t = ce;
  11.    } catch (ExecutionException ee) {
  12.      t = ee.getCause();
  13.    } catch (InterruptedException ie) {
  14.      Thread.currentThread().interrupt(); // ignore/reset
  15.    }
  16.   }
  17.   // 非Future类型
  18.   if (t != null)
  19.    System.out.println(t);
  20. }
  21. }
复制代码
注意:实现afterExecute方法时,要正确嵌套多个覆盖,子类通常应在此方法的开头调用super.afterExecute,以确保不会破坏其他父类方法的实现。
源码解析
在ThreadPoolExecutor中,线程任务的实际执行方法是runWorker,如下所示:
  1. // ThreadPoolExecutor#runWorker
  2. final void runWorker(Worker w) {
  3.   Thread wt = Thread.currentThread();
  4.   Runnable task = w.firstTask; // 实际提交的任务
  5.       // …………
  6.       try {
  7.         beforeExecute(wt, task); // task执行前的操作
  8.         Throwable thrown = null; // 异常信息保存
  9.         try {
  10.           task.run(); // 执行任务
  11.         } catch (RuntimeException x) {
  12.           thrown = x; throw x;
  13.         } catch (Error x) {
  14.           thrown = x; throw x;
  15.         } catch (Throwable x) {
  16.           thrown = x; throw new Error(x);
  17.         } finally {
  18.           afterExecute(task, thrown); // task执行后的操作,也就包括了异常的捕获工作
  19.         }
  20.       } finally {
  21.         task = null;
  22.         w.completedTasks++;
  23.         w.unlock();
  24.       }
  25.     // ……
  26. }
复制代码
afterExecute方法在ThreadPoolExecutor,并没有进行任何操作,就是对异常的线程静默处理
  1. // ThreadPoolExecutor#afterExecute
  2. protected void afterExecute(Runnable r, Throwable t) { }
复制代码
Callable类型的任务,在执行时会自己捕获并维护执行中的异常。
  1. // AbstractExecutorService#submit
  2. public <T> Future<T> submit(Callable<T> task) {
  3.   if (task == null) throw new NullPointerException();
  4.   RunnableFuture<T> ftask = newTaskFor(task);
  5.   execute(ftask);
  6.   return ftask;
  7. }
  8. // Callable任务会被封装成FutureTask
  9. protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  10.   return new FutureTask<T>(callable);
  11. }
复制代码
在FutureTask的run方法中,他们内部会将整个任务用try-catch给包起来。因此,也不会抛出Callable#run执行的内部异常。
  1. // FutureTask#run
  2. public void run() {
  3. // ……
  4.   try {
  5.     Callable<V> c = callable;
  6.     // ……
  7.       // 这里将Callable的执行过程产生的异常都捕获了
  8.       try {
  9.         result = c.call();
  10.         ran = true;
  11.       } catch (Throwable ex) {
  12.         result = null;
  13.         ran = false;
  14.         setException(ex);
  15.       }
  16.       if (ran)
  17.         set(result);
  18.     }
  19.   } finally {
  20. // ……
复制代码
以上为个人经验,希望能给大家一个参考,也希望大家多多支持中国红客联盟。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

荣誉红客

关注
  • 4004
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行