首页 > 其他分享 >【线程基础】【七】UncaughtExceptionHandler 的使用

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

时间:2024-06-18 09:13:36浏览次数:25  
标签:Thread UncaughtExceptionHandler 基础 线程 处理器 new 异常 public

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;
        }

        @Override
        public 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 封装成了 FutureTask
    RunnableFuture<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则报CancellationException
    if (s >= CANCELLED)
        throw new CancellationException();
    // 执行异常,则抛出ExecutionException
    throw new ExecutionException((Throwable)x);
}

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

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

那么我们上边的疑惑:

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

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

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

3  小结

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

标签:Thread,UncaughtExceptionHandler,基础,线程,处理器,new,异常,public
From: https://www.cnblogs.com/kukuxjx/p/18253615

相关文章

  • 二叉树的基础讲解
    二叉树在遍历,查找,增删的效率上面都很高,是数据结构中很重要的,下面我们来基础的认识一下。(高级的本人还没学,下面的代码用伪代码或C语言写的)我会从树,树的一些专有名词,树的遍历,二叉树,二叉树的遍历以及后面升级的树进行一部分的介绍。树首先我们从最开始的树来进行讲解,我们知道......
  • 【MySQL基础随缘更系列】DCL语句
    文章目录一、DCL概述1.1、什么是DCL1.2、为什么学习DCL二、用户管理2.1、查看用户2.2、创建用户2.3、删除用户三、密码管理3.1、修改用户密码3.2、设置管理员(root)密码四、权限管理4.1、查看用户权限4.2、授权4.3、撤销授权......
  • 【MySQL基础随缘更系列】AB复制
    文章目录mysqlAB复制实战一、mysqlAB复制二、AB复制原理三、master服务器设置3.1、安装mysql并启动3.2、关闭防火墙,selinux3.3、设置时间服务器3.4、修改配置文件设置server-id=N3.5、创建slave连接master的账号,用于取SQL语句四、slave设置4.3、修改配置文件设置s......
  • SQL 入门教程:从基础到实践
    前言SQL(StructuredQueryLanguage)是一种用于管理和操作关系型数据库的标准语言。无论你是测试工程师、开发人员,还是数据分析师,掌握SQL都能帮助你更高效地工作。本文将详细介绍SQL的基本概念、常用语法和实践操作,帮助初学者快速入门。什么是SQL?SQL是一种标准化的语言,用......
  • 多叉树的DFS深度优先遍历,回溯法的基础算法之一
    一、前言多叉树一般用于解决回溯问题。想必大家都学过二叉树,以及二叉树的深度优先遍历和广度优先遍历,我们思考:能不能将二叉树的DFS转化为多叉树的DFS?二、多叉树的结构多叉树的本质,就是一棵普通的树,比如下图:如果忽略将来的变化,那么,这棵树可以认为是一个未满的4叉树。......
  • 嵌入式 Linux 基础:环境配置(Debian 12 安装配置)
    目录一、安装虚拟机1、安装VMwareWorkstationPro注册博通官网注册账号下载VMwareWorkstationPro2、虚拟机安装Debian12下载Debian12镜像虚拟机设置配置(安装)debian12配置debian12环境3、配置Debian12软件设置Flatpak和Flathub安装微信二、其他开发环境配置一、安......
  • 面经梳理-java多线程同步协作
    题目Synchronized和ReentryLock锁锁可以视作访问共享数据的许可证。锁能够保护共享数据以实现线程安全,其作用包括保障原子性、保障可见性和保障有序性。Java平台中的锁包括内部锁(IntrinsicLock)和显式锁(ExplicitLock)。内部锁是通过synchronized关键字实现的;显式锁是通过java.ut......
  • 面经梳理-java多线程其他
    题目Threadlocal使用场景?原理?如何保证内存不泄露?ThreadLocal使用场景不加锁的情况下,多线程安全访问共享变量,每个线程保留共享变量的副本(线程特有对象),每个线程往这个ThreadLocal中读写是线程隔离。ThreadLocal原理Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量th......
  • HTML和CSS基础知识(3)
    一、标签显示模式(display)    (一)、什么是标签显示模式        顾名思义就是标签以什么样的方式进行显示,比如div会自己显示一行,span一行可以显示多个,那如果我们想把div在一行内显示多个如何去做,那就需要用到display显示模式了。而display显示模式有以......
  • 面经梳理-java多线程基础
    题目线程和进程的概念?守护线程是干什么的?常见的守护线程有哪些?线程和进程的概念进程是程序的运行实例,是程序向操作系统申请资源的基本单位,线程是进程的一条执行路径。Java的线程分为两种:用户线程和守护线程。守护线程作用是为其他线程提供服务,如果所有的用户线程死亡,后台线程......