首页 > 其他分享 >多线程的未捕获异常类 UncaughtExceptionHandler 的使用

多线程的未捕获异常类 UncaughtExceptionHandler 的使用

时间:2023-06-03 11:59:11浏览次数:37  
标签:Thread UncaughtExceptionHandler 捕获 start 线程 new 多线程 异常 public

一、需要 UncaughtExceptionHandler 的原因

1. 主线程可轻松的发现异常,子线程的异常比较隐蔽,难以发现

程序运行时,子线程发生了异常,并不影响主线程,也不会终止主线程的程序,主线程将继续执行,这时候子线程的异常可能就不会被察觉,就使得子线程的功能出了问题,但没发现。

代码展示:

/**
 * 单线程时,抛出异常,很容易被发现,然后我们就可以处理异常堆栈
 * 多线程,子线程发生异常,会有什么不同???
 *      子线程发生异常时,不影响主线程,不会终止主线程的程序,主线程将继续执行,子线程的异常信息可能被主线程的日志输出给覆盖掉
 */
public class ExceptionInChildThread implements Runnable {
    @Override
    public void run() {
        throw new RuntimeException();
    }

    //主线程
    public static void main(String[] args) {
        new Thread(new ExceptionInChildThread()).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
    }
}

打印结果:

如图所示,当主线程输出大量信息时,子线程的异常信息就被淹没了。而且子线程的异常不会影响到主线程的运行,所以很难被发现。

2. 子线程异常无法用传统异常捕获方式捕获

(1)当没有捕获子线程的异常时

public class CantCatchDirectly implements Runnable {
    @Override
    public void run() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new CantCatchDirectly(),"线程1").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(),"线程2").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(),"线程3").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(),"线程4").start();
    }
}

打印结果:

可以看到四个子线程实际都抛出异常了,然后接下来我们看看使用 try-catch 尝试捕获异常。

(2)使用 try-catch 捕获子线程的异常

加上 try-catch,期望捕获第一个子线程的异常。如果捕获成功的话会直接进入到 catch 代码块中,余下的子线程234应该不会运行,控制台中应该也只会有第一个子线程的异常信息。

public class CantCatchDirectly implements Runnable {
    @Override
    public void run() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws InterruptedException {
        try {
            new Thread(new CantCatchDirectly(),"线程1").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程2").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程3").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程4").start();
        } catch (RuntimeException e) {
            System.out.println("抓住异常");
            e.printStackTrace();
        }
    }
}

打印结果

结果发现,异常根本就没捕获到,这是为什么呢?

因为 try-catch 只能捕获到当前线程内的异常,执行 try-catch 的是主线程,而异常是发生在子线程中,无法进行捕获;

(3)子线程异常不能捕获的后果与改进

后果:子线程出现异常却没人处理,就会导致子线程的那部分逻辑无法成功执行,可能就会影响到程序的整体功能实现。

改进:如果能对子线程的异常及时发现,我们就可以重启线程,或者直接报警去通知运维人员,从而人工处理该异常,等等这类的补救措施,都会对程序的稳定运行很有利,从而提高代码的健壮性,这时我们就需要用到这个能对未捕获异常进行处理的类 UncaughtExceptionHandler。

二、未捕获异常的解决方案

在使用 UncaughtExceptionHandler 解决未捕获异常之前,我们先尝试另一种解决方案,但是不推荐,只是作为参考,那就是在每个子线程可能出现异常的地方都加上 try-catch

1. 手动在每个run方法里进行try catch (不推荐)

public class CantCatchDirectly implements Runnable {
    @Override
    public void run() {
        try {
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println("捕获异常");
        }
    }

    public static void main(String[] args) throws InterruptedException {
            new Thread(new CantCatchDirectly(),"线程1").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程2").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程3").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程4").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"线程5").start();
    }
}

打印结果:

四个线程都不捕获到了异常,该方法确实能处理每一个子线程的异常,但是这样费时耗力,而且也不一定能把所有的异常点全都考虑到。

2. 利用 UncaughtExceptionHandler (推荐)

在 UncaughtExceptionHandler 接口中有且仅有一个方法 void uncaughtException(Thread t,Throwable e);

异常处理器的调用策略

默认情况下,异常处理器中是没有 handler,所以会直接打印出异常堆栈信息,所以我们需要自己设置一个全局 handler,也就是自己 实现一个 UncaughtExceptionHandler 。

(1)自定义 UncaughtExceptionHanlder

/**
 *      自定义 UncaughtExceptionHandler
 */
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
    private String name;

    public MyUncaughtExceptionHandler(String name) {
        this.name = name;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING,"线程异常:"+t.getName(),e);
        System.out.println(name+"我捕获了异常"+t.getName()+"异常名字:"+e);
    }
}

(2) 使用自定义 UncaughtExceptionHandler

也就是将我们自己实现的 MyUncaughtExceptionHandler 放进 DefaultUncaughtExceptionHandler 中,这样线程在遇到未捕获异常时,会优先执行该类中的逻辑。

/**
 *  使用自定义的 UncaughtExceptionHandler
 */
public class UseMyUncaughtExceptionHandler implements Runnable {
    @Override
    public void run() {
            throw new RuntimeException();
    }

    public static void main(String[] args) throws InterruptedException {
        //设置自定义异常处理器
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("我们自定义的线程异常处理器"));

        new Thread(new UseMyUncaughtExceptionHandler(),"线程1").start();
        Thread.sleep(300);
        new Thread(new UseMyUncaughtExceptionHandler(),"线程2").start();
        Thread.sleep(300);
        new Thread(new UseMyUncaughtExceptionHandler(),"线程3").start();
        Thread.sleep(300);
        new Thread(new UseMyUncaughtExceptionHandler(),"线程4").start();
        Thread.sleep(300);
        new Thread(new UseMyUncaughtExceptionHandler(),"线程5").start();
    }
}

打印结果

可以看到在遇到子线程中的未捕获异常时,就会执行 MyUncaughtExceptionHandler 类中的逻辑,这样如果想把异常通知给告警系统或者封装成一个提示语返回给前端,都可以在这个类中进行实现。

三、课后测验

1. 如何处理全局异常?为什么要全局异常处理?不处理行不行?

通过实现一个全局的UncaughtExceptionHandler接口,自定义一个全局处理器;

打印日志方便我们后期的维护,也可以实现返回给前端的统一提示信息;

不处理不行,不处理的话程序会抛出异常子线程中断,主线程不影响,会继续运行。

2. run方法是否可以抛出异常?如果抛出异常,线程的状态会怎么样?

run方法是已经声明好的,固定的,没有声明异常,便不能够向外抛出异常了,只能自己处理try-catch;

如果run方法内部 throws RuntimeException,又没有进行 try-catch 捕获,那么程序会抛出异常,终止运行,打印出异常堆栈。

3. 线程中如何处理某个未处理异常?

通过实现一个全局的UncaughtExceptionHandler接口;

文章来源:多线程的未捕获异常类 UncaughtExceptionHandler 的使用

个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!

标签:Thread,UncaughtExceptionHandler,捕获,start,线程,new,多线程,异常,public
From: https://www.cnblogs.com/javazhizhe/p/17453741.html

相关文章

  • 多线程安全的案例展示与解决方案
    一、概念1.什么是线程安全当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。通俗来说就是:不管业务中遇到怎么的多个线......
  • 多线程-线程池与java内存模型
    多线程-线程池与java内存模型线程池的使用(思路:什么是线程池->他的基本构造以及参数含义->如何使用,使用过程中需要注意什么->有哪些好用的工具类)线程池的基笨概念:首先看一下的继承关系,其次看他的状态,它是利用int的高三位表示状态,比如111表示能接受任务,具体看下面第二章图接下来看......
  • SpringCloud大文件分片上传/多线程上传
    ​ 我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。这次项目的需求:支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,ie8,ie9,Chrome,Firefox,360安全浏览器,并且刷新浏览器后仍然能够续传,重启浏览器(关闭......
  • 可迭代对象、迭代器对象、异常捕获、for循环的原理、迭代取值和索引取值的对比
    整体构思:1.可迭代对象什么是迭代? 迭代言外之意就是更新换代,每次的更新都是要依赖于上一次的结果什么又是可迭代对象呢? ```就是内置有__iter__()方法的都可以称之为是可迭代对象``` "内置":可以直接点出来的就称之为是内置,既然可以点出来就可以直接拿来使用......
  • 不使用第三方框架编写的多线程断线续传功能
    一、背景最近需要个断线续传功能,但是觉得一些框架不太适合,所以基于原理编写了一个多线程断线续传功能支持技术分享,但是复制和转发我的博客时候请标明出处,谢谢 javascript:void(0)二、断线续传的个人理解:1、断线续传在个人理解,其实就是在出现正常下载流程之外的事情的时候,保存好当......
  • SpringBoot大文件分片上传/多线程上传
    ​ 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数下面直接贴代码吧,一些难懂的我大部分都加上注释了:上传文件实体类:看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。pub......
  • SpringMVC大文件分片上传/多线程上传
    ​ javaweb上传文件上传文件的jsp中的部分上传文件同样可以使用form表单向后端发请求,也可以使用ajax向后端发请求    1.通过form表单向后端发送请求         <formid="postForm"action="${pageContext.request.contextPath}/UploadServlet"method="post"e......
  • PHP大文件分片上传/多线程上传
    ​ PHP用超级全局变量数组$_FILES来记录文件上传相关信息的。1.file_uploads=on/off 是否允许通过http方式上传文件2.max_execution_time=30 允许脚本最大执行时间,超过这个时间就会报错3.memory_limit=50M 设置脚本可以分配的最大内存量,防止失控脚本占用过多内存,此......
  • .NET大文件分片上传/多线程上传
    ​ 一、概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载。在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了。一般断点下载时才用到Range和Content-Range实体头。HTTP协议本身不支持断点上传,需要自己实现。 二、Range  用于请求头......
  • C# Web大文件分片上传/多线程上传
    ​ 以ASP.NETCoreWebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API,包括文件的上传和下载。 准备文件上传的API #region 文件上传  可以带参数        [HttpPost("upload")]        publicJsonResultuploadProject(I......