【线程基础】【七】UncaughtExceptionHandler 的使用

news/2024/10/1 15:30:58

1  前言

我们平时在 Java 中处理异常的时候,通常的做法是使用try-catch-finally来包含代码块,但是Java自身还有一种方式可以处理就是使用UncaughtExceptionHandler,本节我们就来看看。

2  UncaughtExceptionHandler

2.1  认识

当 JVM 检测出某个线程由于未捕获的异常而终结的情况时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器。这个是 JDK1.5 开始出现的,它是位于 Thread 类里的一个内部接口:

@FunctionalInterface
public interface UncaughtExceptionHandler {/*** Method invoked when the given thread terminates due to the* given uncaught exception.* <p>Any exception thrown by this method will be ignored by the* Java Virtual Machine.* @param t the thread* @param e the exception*/void uncaughtException(Thread t, Throwable e);
}

可以通过以下实例方法来为每个线程设置一个UncaughtExceptionHandler:

// 给某个线程单独设置异常处理器
thread.setUncaughtExceptionHandler(UncaughtExceptionHandler handler);

或者通过以下静态方法来设置全局默认的UncaughtExceptionHandler:

// 全局的默认异常处理器
Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler);

这些异常处理器中,只有一个将会被调用——JVM首先搜索每个线程的异常处理器,若没有,则搜索该线程的ThreadGroup的异常处理器。ThreadGroup中的默认异常处理器实现是将处理工作逐层委托给上层的ThreadGroup,直到某个ThreadGroup的异常处理器能够处理该异常,否则一直传递到顶层的ThreadGroup。顶层ThreadGroup的异常处理器委托给默认的系统处理器(如果默认的处理器存在,默认情况下为空),否则把栈信息输出到System.err。

2.2  简单实践

下面我们来尝试一个:

/*** @author: kuku* @description*/
public class UncaughtTest {public static void main(String[] args) {// 创建一个线程Thread thread = new Thread(() -> {// 这里直接抛一个错throw new RuntimeException("出错了");});// 设置它的异常处理器thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(String.format("%s:发生了异常,e=%s", t.getName(), e.getMessage()));});// 启动线程
        thread.start();}
}

可以看到异常被异常处理器进行处理了:

我们再来个全局默认的和单独自己的:

/*** @author: kuku* @description*/
public class UncaughtTest {public static void main(String[] args) {Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(String.format("我是全局默认的处理器,t=%s,e=%s", t.getName(), e.getMessage()));});// 创建一个线程Thread thread = new Thread(() -> {// 这里直接抛一个错throw new RuntimeException("出错了");});// 设置它自己的异常处理器thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {System.out.println(String.format("自己的异常处理器%s:发生了异常,e=%s", t.getName(), e.getMessage()));});// 启动线程
        thread.start();// main 线程也抛一个异常throw new RuntimeException("main抛错");}
}

main 线程没有自己的异常处理器,就走线程默认的,Thread-0有自己的就用自己的(就近原则):

2.3  搭配线程池

我们平时使用线程基本都是线程池,那么它怎么跟我们的线程池搭配使用呢?就在我们线程池的 ThreadFactory里可以引进:

/*** @author: kuku* @description*/
public class UncaughtTest {// 创建一个线程池private static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(10,10,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000),new MyThreadFactory((Thread t, Throwable e) ->{System.out.println(String.format("%s:发生了异常,e=%s", t.getName(), e.getMessage()));}));private static class MyThreadFactory implements ThreadFactory {private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;MyThreadFactory(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {this.uncaughtExceptionHandler = uncaughtExceptionHandler;}@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);// 设置异常处理器
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);return thread;}}public static void main(String[] args) {POOL_EXECUTOR.execute(() -> {throw new RuntimeException("1");});POOL_EXECUTOR.execute(() -> {throw new RuntimeException("2");});POOL_EXECUTOR.execute(() -> {throw new RuntimeException("3");});POOL_EXECUTOR.execute(() -> {throw new RuntimeException("4");});}
}

看效果:

这里线程池执行任务,我用的 execute,那当我换成 submit 还会达到同样的效果么?

public static void main(String[] args) throws ExecutionException, InterruptedException {Future<Object> one = POOL_EXECUTOR.submit(() -> {throw new RuntimeException("1");});System.out.println(one.get());
}

我们看执行结果告诉我们,get的时候遇到了ExecutionException,并且也没走我们的默认异常处理器。

并且当我们不 get() 获取结果的时候,什么报错也不会有,这是为什么?我们来看看:

我们首先看看 submit 方法:

public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();// 把我们的task 封装成了 FutureTaskRunnableFuture<T> ftask = newTaskFor(task);// 执行
    execute(ftask);return ftask;
}

那我们再看看 FutureTask 的 run 方法:

public void run() {
...try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;// 我们抛出的异常 会被这里吞掉} catch (Throwable ex) {result = null;ran = false;setException(ex);}
...
}protected void setException(Throwable t) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = t;UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();}
}

继续再来看看Future.get方法的实现:

public V get() throws InterruptedException, ExecutionException {int s = state;// 如果任务没有结束,则等待结束if (s <= COMPLETING)s = awaitDone(false, 0L);// 如果执行结束,则报告执行结果return report(s);
}@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {Object x = outcome;// 如果执行正常,则返回结果if (s == NORMAL)return (V)x;// 如果任务被取消,调用get则报CancellationExceptionif (s >= CANCELLED)throw new CancellationException();// 执行异常,则抛出ExecutionExceptionthrow new ExecutionException((Throwable)x);
}

在执行任务出现异常之后,异常存到了一个 outcome 字段中,只有在调用 get 方法获取 FutureTask 结果的时候,才会以 ExecutionException 的形式重新抛出异常。

如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。所以,通过submit提交到线程池的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分,因此不会交由异常处理器来处理。

那么我们上边的疑惑:

(1)当线程发生异常的时候,为什么不走默认的异常处理器? 因为 FutureTask 把我们抛出的异常捕获掉了,所以线程就不会异常,就走不到我们设置的异常处理器

(2)当调用 get() 获取的结果,自然就是根据线程实际执行的结果,如果有执行异常,会以ExecutionException 的形式重新抛出异常,跟我们的异常处理器搭不上关系了。

所以要记住 submit 和 直接 execute 的差异。

3  小结

好啦,本节关于线程的异常处理器就看到这里,有理解不对的地方欢迎指正哈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/44995.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

探索Semantic Kernel内置插件:深入了解ConversationSummaryPlugin的应用

前言 经过前几章的学习我们已经熟悉了Semantic Kernel 插件的概念,以及基于Prompts构造的Semantic Plugins和基于本地方法构建的Native Plugins。本章我们来讲解一下在Semantic Kernel 中内置的一些插件,让我们避免重复造轮子。 内置插件 Semantic Kernel 有非常多的预定义插…

攀登不止,华为数据库论文入选SIGMOD 2024,技术创新再谱新篇

继ICDE 17篇论文入选后,华为多篇论文再次入选SIGMOD 2024, 顶会入选论文已超过100篇。本文分享自华为云社区《攀登不止,华为数据库论文入选SIGMOD 2024,技术创新再谱新篇》,作者:GaussDB 数据库。 6月9日-14日,2024年数据管理国际会议SIGMOD(ACM SIGMOD/PODS Internati…

Rust性能分析之测试及火焰图,附(lru,lfu,arc)测试

好的测试用例及性能测试是对一个库的稳定及优秀的重要标准,尽量的覆盖全的单元测试,能及早的发现bug,使程序更稳定。性能测试,在编写代码后,单元测试及性能测试是重要的验收点,好的性能测试可以让我们提前发现程序中存在的问题。 测试用例 在Rust中,测试通常有两部分,一…

FreeRTOS简单内核实现7 阻塞链表

增加阻塞链表和溢出阻塞链表,完善 RTOS 内核调度流程0、思考与回答 0.1、思考一 如何处理进入阻塞状态的任务? 为了让 RTOS 支持多优先级,我们创建了多个就绪链表(数组形式),用每一个就绪链表表示一个优先级,对于阻塞状态的任务显然要从就绪链表中移除,但是阻塞状态的任…

零基础写框架(3): Serilog.NET 中的日志使用技巧

.NET 中的日志使用技巧 Serilog Serilog 是 .NET 社区中使用最广泛的日志框架,所以笔者使用一个小节单独讲解使用方法。 示例项目在 Demo2.Console 中。 创建一个控制台程序,引入两个包: Serilog.Sinks.Console Serilog.Sinks.File除此之外,还有 Serilog.Sinks.Elasticsear…

危急值上报及闭环管理全解析

什么是危急值制度? 危急值制度是指对提示患者处于生命危急状态的检查、检验结果建立复核、报告、记录等管理机制,以保障患者安全的制度。 管理体系(组织体系+制度建设+管理要素+宣教培训) 针对管理体系中的组织体系、制度建设、管理要素、宣教培训多为线下的制度、流程建立。…

手术分级管理制度

01手术分级管理体系 ● 医院手术分级管理实行院、科两级负责制 ● 医院医疗技术临床应用管理委员会总体负责全院手术分级管理工作,日常工作由医务办公室负责组织、协调,主要职责包括: (一)制定手术分级管理制度规范,定期检查提出改进要求 (二)审定手术分级管理目录,定…