点击上方 一个优秀的废人,选择 设为星标

优质文章,及时送达


juejin.im/post/6844903997388636174

之前使用线程执行任务的时候,总是忽略了线程异常的处理,直到最近看书~

线程出现异常测试

任务类:Task.java

public class Task implements Runnable {  private int i;

  public Task(int i) {    this.i = i;  }

  @Override  public void run() {    if (i == 5) {      //System.out.println("throw exception");      throw new IllegalArgumentException();    }    System.out.println(i);  }}

如果 i==5,将抛出一个异常线程测试类:TaskTest.java

public class TestTask {  public static void main(String[] args) {    int i = 0;    while (true) {      if (i == 10) break;      try {        new Thread(new Task(i++)).start();      } catch (Exception e) {        System.out.println("catch exception...");      }    }  }}

通过使用 try-catch,尝试对抛出的异常进行捕获测试结果

Connected to the target VM, address: '127.0.0.1:64551', transport: 'socket'012346789Exception in thread "pool-1-thread-1" java.lang.IllegalArgumentException  at com.h2t.study.thread.Task.run(Task.java:21)  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)  at java.lang.Thread.run(Thread.java:748)

异常没有被捕获,只是在控制台打印了异常,并且不影响后续任务的执行 emmmm 这是为什么呢,捕获不到异常就不知道程序出错了,到时候哪天有个任务不正常排查都排查不到,这样是要不得的。看一下 Thread 这个类,有个叫 dispatchUncaughtException 的方法,作用如其名,分发未捕获的异常,把这段代码揪出来:Thread#dispatchUncaughtException

private void dispatchUncaughtException(Throwable e) {  getUncaughtExceptionHandler().uncaughtException(this, e);}

find usage 是找不到该方法在哪里调用的,因为这个方法只被 JVM 调用 Thread#getUncaughtExceptionHandler: 获取 UncaughtExceptionHandler 接口实现类

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

UncaughtExceptionHandler 是 Thread 中定义的接口,在 Thread 类中 uncaughtExceptionHandler 默认是 null,因此该方法将返回 group,即实现了 UncaughtExceptionHandler 接口的 ThreadGroup 类 UncaughtExceptionHandler#uncaughtException: ThreadGroup 类的 uncaughtException 方法实现

public void uncaughtException(Thread t, Throwable e) {    if (parent != null) {        parent.uncaughtException(t, e);    } else {        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);        }    }}

因为在 Thread 类中没有对 group【parent】和 defaultUncaughtExceptionHandler【Thread.getDefaultUncaughtExceptionHandler】进行赋值,因此将进入最后一层条件,将异常打印到控制台中,对异常不做任何处理。整个异常处理器调用链如下:

异常处理器调用链

首先判断默认异常处理器【defaultUncaughtExceptionHandler】是不是为 null,在判断线程组异常处理器【group】是不是为 null,在判断自定义异常处理器【uncaughtExceptionHandler】是不是为 null,都为 null 则在控制台打印异常

线程异常处理

分析了一下源码就知道如果想对任务执行过程中的异常进行处理一个就是让 ThreadGroup 不为 null,另外一种思路就是让 UncaughtExceptionHandler 类型的变量值不为 null。

  • 异常处理器:ExceptionHandler.java
private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {  @Override  public void uncaughtException(Thread t, Throwable e) {    System.out.println("异常捕获到了:" + e);  }}
  • 设置默认异常处理器
Thread.setDefaultUncaughtExceptionHandler((t, e) -> System.out.println("异常捕获到 了: " + e));int i = 0;while (true) {  if (i == 10) break;  Thread thread = new Thread(new Task(i++));  thread.start();}

打印结果:

02139674异常捕获到了:java.lang.IllegalArgumentException8

通过设置默认异常就不需要为每个线程都设置一次了

  • 设置自定义异常处理器
Thread t = new Thread(new Task(i++));t.setUncaughtExceptionHandler(new ExceptionHandler());

打印结果:

024异常捕获到了:java.lang.IllegalArgumentException613798

设置线程组异常处理器

MyThreadGroup myThreadGroup = new MyThreadGroup("测试线程线程组");Thread t = new Thread(myThreadGroup, new Task(i++))

自定义线程组:MyThreadGroup.java

private static class MyThreadGroup extends ThreadGroup {  public MyThreadGroup(String name) {    super(name);  }

  @Override  public void uncaughtException(Thread t, Throwable e) {    System.out.println("捕获到异常了:" + e);  }}

打印结果:

120436789捕获到异常了:java.lang.IllegalArgumentException

线程组异常捕获处理器很适合为线程进行分组处理的场景,每个分组出现异常的处理方式不相同 设置完异常处理器后异常都能被捕获了,但是不知道为什么设置异常处理器后任务的执行顺序乱了,难道是因为为每个线程设置异常处理器的时间不同【想不通】

线程池异常处理

一般应用中线程都是通过线程池创建复用的,因此对线程池的异常处理就是为线程池工厂类【ThreadFactory 实现类】生成的线程添加异常处理器

  • 默认异常处理器
Thread.setDefaultUncaughtExceptionHandler(new  ExceptionHandler()); ExecutorService es = Executors.newCachedThreadPool();es.execute(new Task(i++))
  • 自定义异常处理器
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,        0L, TimeUnit.MILLISECONDS,        new LinkedBlockingQueue());threadPoolExecutor.setThreadFactory(new MyThreadFactory());threadPoolExecutor.execute(new Task(i++));

自定义工厂类:MyThreadFactory.java

private static class MyThreadFactory implements ThreadFactory {  @Override  public Thread newThread(Runnable r) {    Thread t = new Thread();    //自定义UncaughtExceptionHandler    t.setUncaughtExceptionHandler(new ExceptionHandler());    return t;   }}

设计原则,为什么要由线程自身进行捕获

来自 JVM 的设计理念 " 线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部 "。因此在 Java 中,线程方法的异常【即任务抛出的异常】,应该在线程代码边界之内处理掉,而不应该在线程方法外面由其他线程处理

线程执行 Callable 任务

前面介绍的是线程执行 Runnable 类型任务的情况,众所周知,还有一种有返回值的 Callable 任务类型 测试代码:TestTask.java

public class TestTask {    public static void main(String[] args) {        int i = 0;        while (true) {            if (i == 10) break;            FutureTask task = new FutureTask<>(new CallableTask(i++));            Thread thread = new Thread(task);            thread.setUncaughtExceptionHandler(new ExceptionHandler());            thread.start();        }    }private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {            System.out.println("异常捕获到了:" + e);        }    }}

打印结果:

Disconnected from the target VM, address: '127.0.0.1:64936', transport: 'socket'012346789

观察结果,异常没有被捕获,thread.setUncaughtExceptionHandler (new ExceptionHandler ()) 方法设置无效,emmmmm,这又是为什么呢,在问为什么就是十万个为什么儿童了。查看 FutureTask 的 run 方法,FutureTask#run:

public void run() {        if (state != NEW ||            !UNSAFE.compareAndSwapObject(this, runnerOffset,                                         null, Thread.currentThread()))            return;        try {            Callable 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);                }if (ran)                    set(result);            }        } finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()            runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;if (s >= INTERRUPTING)                handlePossibleCancellationInterrupt(s);        }    }

FutureTask#setException:

protected void setException(Throwable t) {    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {      //将异常设置给outcome变量      outcome = t;      //设置任务的状态为EXCEPTIONAL      UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state      finishCompletion();    }}

看到 catch 这段代码,当执行任务捕获到异常的时候,会将任务的处理结果设置为 null,并且调用 setException 方法对捕获的异常进行处理,因为 setUncaughtExceptionHandler 只对未捕获的异常进行处理,FutureTask 已经对异常进行了捕获处理,因此调用 setUncaughtExceptionHandler 捕获异常无效 对任务的执行结果调用 get 方法:

int i = 0;while (true) {    if (i == 10) break;    FutureTask task = new FutureTask<>(new CallableTask(i++));    Thread thread = new Thread(task);    thread.setUncaughtExceptionHandler(new ExceptionHandler());    thread.start();//打印结果try {    System.out.println(task.get());    } catch (Exception e) {      System.out.println("异常被抓住了, e: " + e);    }}

执行结果将会将捕获到的异常打印出来,执行结果:

01234异常被抓住了, e: java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException67Disconnected from the target VM, address: '127.0.0.1:50900', transport: 'socket'89

FutureTask#get:

public V get() throws InterruptedException, ExecutionException {    int s = state;    if (s <= COMPLETING)      //未完成等待任务执行完成      s = awaitDone(false, 0L);    return report(s);}

FutureTask#report:

private V report(int s) throws ExecutionException {    Object x = outcome;    if (s == NORMAL)      return (V)x;    if (s >= CANCELLED)      throw new CancellationException();    throw new ExecutionException((Throwable)x);}

outcome 在 setException 方法中被设置为了异常,并且 s 为 state 的状态最终 8 被设置为 EXCEPTIONAL,因此方法将捕获的任务抛出【new ExecutionException ((Throwable) x)】

总结:

Callable 任务抛出的异常能在代码中通过 try-catch 捕获到,但是只有调用 get 方法后才能捕获到

-END-

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「一个优秀的废人」,关注后回复「 1024」送你一套完整的 java 教程。回复「 电子书」送你 100+ 本 Java 电子书。

教程节选

illegalargumentexception是什么异常_线程出现异常!应该如何处理?相关推荐

  1. numberformatexception是什么异常_处理Java异常的9个最佳实践

    Java中的异常处理不是一个简单的主题.初学者发现很难理解,甚至有经验的开发人员也可以花几个小时讨论如何以及应该抛出或处理哪些异常. 这就是为什么大多数开发团队都有自己的如何使用它们的规则.如果你是一 ...

  2. classcastexception异常_内部类、异常以及 LeetCode 每日一题

    1 内部类 内部类的作用: 内部类提供了更好的封装,可以把内部类隐藏于外部类之内,不允许同一个包中的其他类访问该类.(例如给"牛"这个类组合一个"牛腿",则可以 ...

  3. classnotfoundexception是什么异常_大佬说“异常信息”是优秀程序员编写代码的宝贵财富,这是真的吗...

    嗯嗯.......大佬给我看看我的代码呢,到底错哪里了?大佬走过来,一波骚操作,安排得巴巴适适的.走时撂下一句:哥仔建议你看一下控制台,那么简单的问题,记住异常信息就是你宝贵的财富. try 用来指定 ...

  4. dll domodal运行时异常_软件运行异常时的多种排查思路与方法

    软件发生异常,排查起来毫无头绪和思路时,该怎么办呢?结合多年的开发经验,我来告诉你们几个常用的方法,不妨用这些方法去试一试!希望能帮到你们. 1.通过安装软件不同时间的版本对比一下 这个方法有点笨,但 ...

  5. trycatch 不能捕获运行时异常_软件运行异常时的多种排查思路与方法

    软件发生异常,排查起来毫无头绪和思路时,该怎么办呢?结合多年的开发经验,我来告诉你们几个常用的方法,不妨用这些方法去试一试!希望能帮到你们. 1.通过安装软件不同时间的版本对比一下 这个方法有点笨,但 ...

  6. java 数组越界异常_数组越界异常 求解决!!!

    源自:4-3 滚动状态判断与处理 数组越界异常 求解决!!! package com.example.imooc; import java.io.BufferedInputStream; import ...

  7. java编译异常和运行时异常_浅谈异常结构图、编译期异常和运行期异常的区别...

    异常处理一般有2种方式,要么捕获异常try-catch,要么抛出异常throws 如果一个方法后面抛出一个运行时期异常(throws RuntimeException),调用者无须处理 如果一个方法后 ...

  8. JavaSE个人复习式整理知识点之异常、线程

    异常.线程 1 异常 1.1 异常概念 1.2 异常体系 1.3 异常分类 1.4 异常的产⽣过程解析 2 异常的处理 2.1 抛出异常throw 2.2 Objects⾮空判断 2.3 声明异常th ...

  9. kotlin协程硬核解读(5. Java异常本质协程异常传播取消和异常处理机制)

    版权声明:本文为openXu原创文章[openXu的博客],未经博主允许不得以任何形式转载 文章目录 1. 异常的本质 1.1 操作系统.程序.JVM.进程.线程 1.2 异常方法调用栈 1.3 ja ...

最新文章

  1. 【深度学习】基于Pytorch进行深度神经网络计算(一)
  2. opencv 车牌切割
  3. POJ 2386 Lake Counting DFS水水
  4. DistCp迁移Hive数据过程中源集群增加删除文件等场景测试
  5. 高颜值网易云音乐第三方播放器 YesPlayMusic Mac中文版 支持m1
  6. Ogre 3d 工具集
  7. github中的各种操作
  8. Java jdk下载及安装
  9. 谷歌地球(google earth)中文专业版官方下载
  10. 一文带你认识HTML
  11. UG二次开发PK内核获得面积
  12. SpringCloud(2)--服务调用
  13. 解决Mac绿联 拓展坞 网线接口无法使用问题
  14. Ubuntu PPPoE拨号上网
  15. 想多活几年吗??在中国到底什么能吃什么不能吃!(转)
  16. 如何给孩子选绘本:绘本筛选实践分享
  17. DB数据变更缓存分布式更新的zk分布式锁解决方案
  18. 计算机应用研究》专业刊物论文,《计算机应用研究》CSCD核心期刊 2017年03期目录...
  19. 985毕业,35岁创业失败,36岁回炉40岁被裁,中年夫妻无业咋办?
  20. HTML 标签(tag)

热门文章

  1. MySQL高级能量预警
  2. JavaScriptjQuery.变量作用域
  3. 关于Vue中常用的工具函数封装
  4. centos7快速搭建LAMP
  5. BZOJ2498 : Xavier is Learning to Count
  6. Supporting Python 3(支持python3)——常见的迁移问题
  7. 11g RMAN Restore archivelog用法
  8. OGNL中的s:property /标签
  9. src源代码生成html格式文档
  10. hadoop集群的白名单和黑名单示例说明