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

 更新时间:2022年10月28日 09:35:04   作者:极光雨雨  
这篇文章主要介绍了Java如何主动从当前线程获取异常信息,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

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

使用场景

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

写法

当前方法throw 了 异常,即改方法存在异常的情况,则可以使用如下方式获取当前线程中的异常:

// 主动获取当前线程异常栈信息
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

// 以日志的方式打出  \n  每一行错误栈信息结束后换行
log.error(StringUtils.join(stackTraceElements, '\n'));

Java捕获并处理线程异常:Thread及ThreadPoolExecutor线程池异常捕获

通过Thread.UncaughtExceptionHandler捕获线程异常

Thread.UncaughtExceptionHandler类的作用:捕获并处理线程run方法抛出的异常。

使用示例

为单个线程设置异常捕获

Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
    System.out.println("报错线程:" + t.getName());
    System.out.println("线程抛出的异常:" + e);
};
Thread thread = new Thread(() -> {
    throw new RuntimeException("throw a new Exception ! ");
});
thread.setUncaughtExceptionHandler(exceptionHandler); // 设置异常处理器
thread.start();

如果项目中,全局的Thread线程处理异常的方式都相同,那么可以设置一个全局的异常捕获类。

Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
    System.out.println("报错线程:" + t.getName());
    System.out.println("线程抛出的异常:" + e);
};
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);

通过这种方式,后续的Thread都可以公用这个异常处理类。如果需要用其它方式处理异常时,只需要实现1中的内容即可。

部分源码解析

UncaughtExceptionHandler源码
// Thread.UncaughtExceptionHandler 
@FunctionalInterface
public interface UncaughtExceptionHandler {
    void uncaughtException(Thread t, Throwable e);
}

Thread中的UncaughtExceptionHandler属性

// 作用于当个Thread线程
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// static修饰,其可以作用于所有Thread线程
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

在Thread中,会首先提供Thread私有的异常处理类,然后才是全局

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}

实现原理

当线程由于未捕获的异常而即将终止时,Java虚拟机将使用调用线程的getUncaughtExceptionHandler方法,以此来获取UncaughtExceptionHandler类,并执行其对异常的处理方法。

如果一个线程没有显式实现UncaughtExceptionHandler ,那么它的ThreadGroup对象将充当它的UncaughtExceptionHandler类。

// Thread#getUncaughtExceptionHandler
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}

ThreadGroup实现了UncaughtExceptionHandler类。

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    // ……
    public void uncaughtException(Thread t, Throwable e) {
        // 父级ThreadGroup的处理方法
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            // Thread 的全局默认处理方法
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
    // ……
}

ThreadPoolExecutor线程池异常捕获

使用示例

要捕获ThreadPoolExecutor线程池中的线程执行异常,需要实现被protected修饰的方法afterExecute,在该方法里面处理异常即可。

// ThreadPoolExecutor#afterExecute
class ExtendedExecutor extends ThreadPoolExecutor {
  protected void afterExecute(Runnable r, Throwable t) {
    super.afterExecute(r, t);
    // 如果Runnable是Future类型,那么异常将会直接通过Future返回
    if (t == null && r instanceof Future<?>) {
      try {
        Object result = ((Future<?>) r).get();
      } catch (CancellationException ce) {
          t = ce;
      } catch (ExecutionException ee) {
          t = ee.getCause();
      } catch (InterruptedException ie) {
          Thread.currentThread().interrupt(); // ignore/reset
      }
    }
    // 非Future类型
    if (t != null)
      System.out.println(t);
  }
}

注意:实现afterExecute方法时,要正确嵌套多个覆盖,子类通常应在此方法的开头调用super.afterExecute,以确保不会破坏其他父类方法的实现。

源码解析

在ThreadPoolExecutor中,线程任务的实际执行方法是runWorker,如下所示:

// ThreadPoolExecutor#runWorker
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; // 实际提交的任务
            // …………
            try {
                beforeExecute(wt, task); // task执行前的操作
                Throwable thrown = null; // 异常信息保存
                try {
                    task.run(); // 执行任务
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // task执行后的操作,也就包括了异常的捕获工作
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        // ……
}

afterExecute方法在ThreadPoolExecutor,并没有进行任何操作,就是对异常的线程静默处理

// ThreadPoolExecutor#afterExecute
protected void afterExecute(Runnable r, Throwable t) { }

Callable类型的任务,在执行时会自己捕获并维护执行中的异常。 

// AbstractExecutorService#submit
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

// Callable任务会被封装成FutureTask
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

在FutureTask的run方法中,他们内部会将整个任务用try-catch给包起来。因此,也不会抛出Callable#run执行的内部异常。

// FutureTask#run
public void run() {
    // ……
    try {
        Callable<V> c = callable;
        // ……
            // 这里将Callable的执行过程产生的异常都捕获了
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
// ……

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Maven 打包项目到私服 (deploy)的配置方法

    Maven 打包项目到私服 (deploy)的配置方法

    这篇文章主要介绍了Maven 打包项目到私服 (deploy)的配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Mybatis有查询结果但存不进实体类的解决方案

    Mybatis有查询结果但存不进实体类的解决方案

    这篇文章主要介绍了Mybatis有查询结果但存不进实体类的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • java interface的两个经典用法

    java interface的两个经典用法

    这篇文章主要为大家详细介绍了java interface的两个经典用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • Java 使用geotools读取tiff数据的示例代码

    Java 使用geotools读取tiff数据的示例代码

    这篇文章主要介绍了Java 通过geotools读取tiff,一般对于tiff数据的读取,都会借助于gdal,本文结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • Java实现JSON与XML相互转换的简明教程

    Java实现JSON与XML相互转换的简明教程

    Java实现复杂数据结构(如嵌套对象、数组)在 JSON 与 XML 之间的相互转换,可以使用 Jackson 和 Jackson XML 扩展库来完成,Jackson 是一个流行的 JSON 处理库,通过 Jackson 的 XML 扩展库,可以实现 JSON 和 XML 之间的转换,需要的朋友可以参考下
    2024-08-08
  • java导出dbf文件生僻汉字处理方式

    java导出dbf文件生僻汉字处理方式

    这篇文章主要介绍了java导出dbf文件生僻汉字处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • SpringBoot Actuator监控的项目实践

    SpringBoot Actuator监控的项目实践

    本文主要结合 Spring Boot Actuator,跟大家一起分享微服务Spring Boot Actuator 的常见用法,方便我们在日常中对我们的微服务进行监控治理,感兴趣的可以了解一下
    2024-01-01
  • 8个Spring事务失效场景详解

    8个Spring事务失效场景详解

    相信大家对Spring种事务的使用并不陌生,但是你可能只是停留在基础的使用层面上。今天,我们就简单来说下Spring事务的原理,然后总结一下spring事务失败的场景,并提出对应的解决方案,需要的可以参考一下
    2022-12-12
  • Java程序死锁问题定位与解决方法

    Java程序死锁问题定位与解决方法

    死锁是一种特定的程序状态,主要是由于循环依赖导致彼此一直处于等待中,而使得程序陷入僵局,相当尴尬,死锁不仅仅发生在线程之间,而对于资源独占的进程之间同样可能出现死锁,本文给大家介绍了Java程序死锁问题定位与解决方法,需要的朋友可以参考下
    2024-11-11
  • Mybatis中强大的resultMap功能介绍

    Mybatis中强大的resultMap功能介绍

    这篇文章主要给大家介绍了关于Mybatis中强大的resultMap功能的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Mybatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06

最新评论