java如何处理多线程异常

一、一个线程在执行过程中发生了异常会怎样?

        那要看我们是否对这个异常进行了处理,如果处理了,那么线程会继续执行,如果没有处理,那么线程会释放掉自己所持有的锁,退出执行,如果这个线程是主线程,那么主线程退出执行了,程序也会停止执行,如果这个线程不是主线程,那么它的退出不会影响到主线程和其他线程,程序会继续执行。但无论这个线程是否是主线程,线程因异常而退出会导致我们的业务执行失败,会影响正常的业务功能,所以我们应该在开发中用合适的方式去处理这些异常。

        java中的异常分为检查时异常和运行时异常两大类,java要求我们必须显示地处理检查时异常,如果不处理,会在编译期报错,而对于运行时异常我们可以处理,也可以不处理,都是可以通过编译的,只是,如果不处理,那么可能在程序的运行期间因为发生RuntimeExeception而导致线程异常退出。那么对于运行时异常我们应该如何处理呢?

        首先,在单线程环境中,我们可以通过try/catch语句块去进行异常的捕获处理,或者是用throw/throws方式将异常向上抛出,而对于抛出的异常,可以由外层的调用方法来进行捕获处理。那么多线程执行过程中发生的异常如何处理呢?

二、处理多线程执行过程中发生的异常

1、Runnable类型任务的异常处理

        因为Runnable的run方法是无法将异常向上抛出的,所以它在执行过程中如果发生了异常,这个异常要么在run方法内部处理,要么由任务线程的UncaughtExceptionHandler来处理,如果我们既没有在run方法内部捕获它,又没为任务线程设置UncaughtExceptionHandler,那么这个异常的发生将导致任务线程异常退出,任务执行失败。

未处理异常的情况:

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("模拟Runnable异常...");
            int num = 1/0;
        }
    });
    thread.start();
}

 

在run方法内部处理异常:

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("模拟Runnable异常...");
            try {
                int num = 1/0;
            }catch (Exception e){
                System.out.println("Runnable任务内部处理异常");
            }
        }
    });
    thread.start();
}

用UncaughtExceptionHandler处理未捕获异常:

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("模拟Runnable异常...");
            int num = 1/0;
        }
    });
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
    thread.start();
}

UncaughtExceptionHandler

        UncaughtExceptionHandler是定义在Thread类中的一个内部接口,它是线程的未捕获异常处理器,它的作用是处理线程执行过程中发生的未捕获异常,当线程在执行过程中发生了异常时,jvm会先去查找当前方法有没有对这个异常做捕获处理,也就是查找当前方法的catch语句块,如果没做捕获处理,则查找当前方法是否声明抛出了这个异常,如果声明抛出这一类异常了,则会沿着方法调用栈往上查找,如果调用栈中各个方法均声明抛出了这一类异常,而一直查找到了栈底却依然没有对于这个异常的捕获处理,则会去查找此线程有没有设置UncaughtExceptionHandler,如果设置了,则由这个UncaughtExceptionHandler处理,如果没设置,则再去查找当前线程所属线程组有没有设置UncaughtExceptionHandler,如果设置了,就由它来处理,如果没设置,则该异常被定位到System.err。Thread类中还定义了一个setUncaughtExceptionHandler方法,用于为线程设置未捕获异常处理器,但是UncaughtExceptionHandler只对Runnable类型的任务线程起作用,当线程执行的是Callable类型的任务时,即便我们为任务线程设置了UncaughtExceptionHandler,它也不会生效。

2、Callable类型任务的异常处理

        UncaughtExceptionHandler在Callable类型的任务线程里是不生效的

public static void main(String[] args) {
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            System.out.println("模拟Callable异常...");
            int num = 1/0;
            return num;
        }
    });
    Thread thread = new Thread(futureTask);
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
    thread.start();
}

        

        可见,当call方法执行过程中发生了未捕获异常时,这个异常也不会被UncaughtExceptionHandler捕获到,为什么呢?我们在main线程中调用一下FutureTask对象的get方法看一下会有怎样的结果:

public static void main(String[] args) {
    FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            System.out.println("模拟Callable异常...");
            int num = 1/0;
            return num;
        }
    });
    Thread thread = new Thread(futureTask);
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
    thread.start();
    try {
        futureTask.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

        可以看到,任务线程中的异常被抛到了get方法的调用线程main线程这里,可见,即便是Callable任务执行过程中发生了异常,这个异常也不会在任务线程里出现,当然也就不能被任务线程的UncaughtExceptionHandler捕获到,它只会在调用FutureTask对象的get方法的线程里出现,并且它被包装成了一个ExecutionException。那么在这个过程中到底发生了什么呢?

FutureTask对于Callable任务异常的处理

        Callable类型的任务线程在被调度到的时候,执行的是FutureTask对象的run方法,而在run方法的内部执行的又是Callable对象的call方法,如果call方法在执行过程中发生了异常,那么这个异常会被run方法捕获处理,FutureTask类有一个Object类型的成员变量outcome,这个成员变量用于存放call方法的执行结果或call方法在执行过程中抛出的异常,而outcome存放的异常在当前的FutureTask对象的get方法中又被重新包装成了一个ExecutionException异常抛到了get方法外部,也就是调用get方法的线程中。源码如下:

再来看一下setException方法的源码:

这就是在任务线程内部捕获不到call方法异常的原因。再看一下get方法:

 

        所以对于Callable任务的异常,我们可以在任务内部【也就是call方法内部】捕获原发类型的异常来处理,或者在FutureTask的get方法的调用线程里捕获ExecutionException类型的异常来处理。

3、线程池中任务异常的处理

        当我们使用的是线程池的execute方法来提交多线程任务时,因为它提交的是Runnable类型的任务,所以我们为任务线程设置的UncaughtExceptionHandler是可以生效的,所以我们可以实现一个ThreadFactory,再实现一个UncaughtExceptionHandler,在ThreadFactory的newThread方法内部,为每一个创建出来的线程都设置这个UncaughtExceptionHandler,让线程使用这个UncaughtExceptionHandler去处理任务执行过程中发生的未捕获异常。

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new LinkedBlockingDeque<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "模拟Runnable异常");
                int num = 1/0;
            }
        });
    }
}

        所以当我们用execute方法往线程池中提交任务时,既可以在任务内部【run方法内部】处理异常,也可以为线程设置UncaughtExceptionHandler来处理任务的未捕获异常。

        但是用submit方法提交到线程池中的任务所抛出的异常却无法被任务线程的UncaughtExceptionHandler所捕获到

比如,当提交的是Callable类型的任务时:

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new ArrayBlockingQueue<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
        threadPoolExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("模拟Callable异常");
                int num = 1/0;
                return num;
            }
        });

    }
}

可见UncaughtExceptionHandler没有生效,再调用一下submit方法返回的Future对象的get方法看一下结果: 

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new ArrayBlockingQueue<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
        Future<Integer> result = threadPoolExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("模拟Callable异常");
                int num = 1/0;
                return num;
            }
        });
        try {
            result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

         Callable任务的异常被包装为ExecutionException异常抛到了调用get方法的main线程中,原因也很简单,submit方法肯定将我们提交的Callable任务封装到了FutureTask对象中执行,而FutureTask对象的run方法对call方法抛出的异常做了捕获处理,将异常放在了自己的outcome变量中保存。

submit方法的源码也确实是这样实现的:

 

FutureTask.run方法源码就不贴了,上面已经贴过了。 

         而即便是我们用submit方法提交的是一个Runnable类型的任务,任务线程的UncaughtExceptionHandler依然不会生效:

public static void main(String[] args) {
    Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("处理线程" + t.getName() + "执行过程中的未捕获异常:" + e.getMessage());
        }
    };
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
            return thread;
        }
    };
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,200L,TimeUnit.MILLISECONDS
            ,new ArrayBlockingQueue<>(2),threadFactory);
    for (int i = 0; i < 5; i++) {
       Future result = threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("模拟Runnable异常");
                int num = 1/0;
            }
        });
        try {
            result.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

         任务异常被包装为ExecutionException抛到了调用Future.get方法的main线程中,而未被任务线程的UncaughtExceptionHandler捕获到。原因是,submit方法用我们传入的Runnable对象构造了一个FutureTask对象去执行,并将Runnable对象转换为Callable类型赋给了这个FutureTask对象的callable成员变量。

源码:

因此这个异常只能在调用Future.get方法的线程中以ExecutionException类型捕获到。

        总结一下,使用execute方法提交的多线程任务,异常可以在任务内部处理,也可以为任务线程设置UncaughtExceptionHandler处理。使用submit方法提交的多线程任务,异常可以在任务内部处理,也可以在调用线程中处理,也就是在调用Future对象的get方法去获取任务结果的线程中处理。

        在实际的开发工作中,我们要根据业务需求去选择使用哪种方式提交多线程任务,当我们选定方式之后,还要去使用合适的策略去处理任务异常。

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

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

相关文章

linux 基于科大讯飞的文字转语音使用

官方文档地址&#xff1a;离线语音合成 Linux SDK 文档 | 讯飞开放平台文档中心 一、SDK下载 1、点击上面官方文档地址的链接&#xff0c;可以跳转到以下界面。 2、点击“普通版”&#xff0c;跳转到以下界面。 3、点击“下载”跳转到以下界面 4、最后&#xff0c;点击“SDK下…

电脑和手机连接酒店的wifi,网络不通导致charles无法抓手机的包

查看苹果手机&#xff0c;连wifi后的ip地址 电脑去ping 手机的ip地址&#xff0c;发现ping不通 解决方案&#xff1a; 应该是酒店wifi的问题&#xff0c;让朋友开个手机热点&#xff0c;电脑和我的手机都连这个热点&#xff0c;就可以抓包了

【vue2】路由之 Vue Router

文章目录 一、安装二、基础使用1、简单的示例2、动态路由2.1 定义动态路径参数2.2 获取动态路径的参数2.3 捕获所有路由 3、嵌套路由4、编程式的导航4.1 router.push4.2 router.replace4.3 router.go(n) 5、命名路由6、重定向 三、进阶1、导航守卫1.1 全局前置守卫1.2 全局后置…

日常学习之:vue + django + docker + heroku 对后端项目 / 前后端整体项目进行部署

文章目录 使用 docker 在 heroku 上单独部署 vue 前端使用 docker 在 heroku 上单独部署 django 后端创建 heroku 项目构建 Dockerfile设置 settings.pydatabase静态文件管理安全设置applicaiton & 中间件配置 设置 requirements.txtheroku container 部署应用 前后端分别部…

SpringBoot整合Xxl-Job实现异步任务调度中心

目录 一、下载 1、源码 2、项目结构 3、模块说明 二、部署任务调度中心 1、创建数据库xxl-job 2、配置数据库 3、启动admin模块 4、打开任务调度中心 三、SpringBoot整合xxl-job 1、导入依赖 2、配置yml文件 3、配置类 4、启动项目 5、任务配置 6、测试 一、下…

Windows 和 Anolis 通过 Docker 安装 Milvus 2.3.4

Windows 10 通过 Docker 安装 Milvus 2.3.4 一.Windows 安装 Docker二.Milvus 下载1.下载2.安装1.Windows 下安装&#xff08;指定好Docker文件目录&#xff09;2.Anolis下安装 三.数据库访问1.ATTU 客户端下载 一.Windows 安装 Docker Docker 下载 双击安装即可&#xff0c;安…

[嵌入式系统-5]:龙芯1B 开发学习套件 -2- LoongIDE 集成开发环境集成开发环境的安装步骤

目录 一、LoongIDE&#xff08;龙芯开发工具集成环境&#xff09;概述 1.1 概述 二、软件开发环境的安装过程 2.0 注意事项 2.1 步骤1&#xff1a;MingW运行环境 2.2 步骤2&#xff1a;安装LoongIDE 2.3 步骤3&#xff1a;安装MIPS工具链 2.4 配置工具链 2.5 重启电脑…

总结NB-IoT模块和单片机的区别

在学习了NB-IoT模块后&#xff0c;紧接着又学习了单片机系统&#xff0c;单片机和NB-IoT模块有什么不同之处呢&#xff0c;总结为以下几点。 大纲如图&#xff1a; 一、硬件层面 1、采用芯片不同&#xff0c; &#xff08;1&#xff09;封装&#xff1a;封装尺寸、方式不同&a…

Qt应用软件【串口篇】串口通信

文章目录 1.串口概述2.串口传输数据的基本原理电信号的传输过程 3.串口的几个概念数据位&#xff08;Data Bits&#xff09;奇偶校验位&#xff08;Parity Bit&#xff09;停止位&#xff08;Stop Bits&#xff09;流控制&#xff08;Flow Control&#xff09;波特率&#xff0…

第九篇【传奇开心果短博文系列】鸿蒙开发技术点案例示例:ArkUI强大的状态管理机制解读

传奇开心果短博文系列 系列短博文目录鸿蒙开发技术点案例示例系列 短博文目录一、前言二、ArkUI强大的状态管理机制介绍三、以官方helloworld示例为例说明ArkUI的状态定义和管理四、以官方 HelloWorld 示例代码为例说明ArkUI状态依赖和自动更新五、以官方helloworld示例代码为例…

PHP语法

#本来是在学命令执行&#xff0c;所以学了学&#xff0c;后来发现&#xff0c;PHP语法和命令执行的关系好像没有那么大&#xff0c;不如直接学php的一些命令执行函数了。# #但是还是更一下&#xff0c;毕竟还是很多地方都要求掌握php作为脚本语言&#xff0c;所以就学了前面的…

多维时序 | Matlab实现DBO-GRU蜣螂算法优化门控循环单元多变量时间序列预测

多维时序 | Matlab实现DBO-GRU蜣螂算法优化门控循环单元多变量时间序列预测 目录 多维时序 | Matlab实现DBO-GRU蜣螂算法优化门控循环单元多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现DBO-GRU蜣螂算法优化门控循环单元多变量时间序列预…

第四十一周:文献阅读+GAN存在的问题和改进

目录 摘要 Abstract 文献阅读&#xff1a;基于Transformer的时间序列生成对抗网络 现有问题 提出方法 相关前提 GAN&#xff08;生成对抗网络&#xff09; Transformer 方法论 时间序列处理 TTS-GAN &#xff08;基于Transformer的时间序列生成对抗网络&#xff09;…

STM32学习笔记(二) —— 调试串口

我们在调试程序时&#xff0c;经常会使用串口打印相关的调试信息&#xff0c;但是单片机串口不能直接与 PC 端的 USB 接口通讯&#xff0c;需要用到一个USB转串口的芯片来充当翻译的角色。我们使用的开发板上有这个芯片&#xff0c;所以在打印调试信息的时候直接使用USB线连接开…

05.领域驱动设计:认识领域事件,解耦微服务的关键

目录 1、概述 2、领域事件 2.1 如何识别领域事件 1.微服务内的领域事件 2.微服务之间的领域事件 3、领域事件总体架构 3.1 事件构建和发布 3.2 事件数据持久化 3.3 事件总线 (EventBus) 3.4 消息中间件 3.5 事件接收和处理 4、案例 5、总结 1、概述 在事件风暴&a…

Jmeter连接数据库报错Cannot load JDBC driver class‘com.mysql.jdbc.Driver’解决

问题产生: 我在用jmeter连接数据库查询我的接口是否添加数据成功时,结果树响应Cannot load JDBC driver class com.mysql.jdbc.Driver 产生原因: 1、连接数据库的用户密码等信息使用的变量我放在了下面,导致没有取到用户名密码IP等信息,导致连接失败 2、jmeter没有JDB…

scrapy的入门使用

1 安装scrapy 命令: sudo apt-get install scrapy或者&#xff1a; pip/pip3 install scrapy2 scrapy项目开发流程 创建项目: scrapy startproject mySpider生成一个爬虫: scrapy genspider itcast itcast.cn提取数据:     根据网站结构在spider中实现数据采集相关内…

MATLAB - 仿真单摆的周期性摆动

系列文章目录 前言 本例演示如何使用 Symbolic Math Toolbox™ 模拟单摆的运动。推导摆的运动方程&#xff0c;然后对小角度进行分析求解&#xff0c;对任意角度进行数值求解。 一、步骤 1&#xff1a;推导运动方程 摆是一个遵循微分方程的简单机械系统。摆最初静止在垂直位置…

2024年数学建模美赛 分析与编程

2024年数学建模美赛 分析与编程 1、本专栏将在2024年美赛题目公布后&#xff0c;进行深入分析&#xff0c;建议收藏&#xff1b; 2、本专栏对2023年赛题&#xff0c;其它题目分析详见专题讨论&#xff1b; 2023年数学建模美赛A题&#xff08;A drought stricken plant communi…

uniapp组件库Card 卡片 的使用方法

目录 #平台差异说明 #基本使用 #配置卡片间距 #配置卡片左上角的缩略图 #配置卡片边框 #设置内边距 #API #Props #Slot #Event 卡片组件一般用于多个列表条目&#xff0c;且风格统一的场景。 #平台差异说明 AppH5微信小程序支付宝小程序百度小程序头条小程序QQ小程…