Java并发编程 - 线程异常处理

《Java编程思想》在讲到异常处理的时候有这样的一段话:

Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception型异常。所以Java程序员关心的基本类型通常是Exception。

从上面这段话可以看到,我们在写代码的时候基本上只用关心Exception。

Exception再细分可分为受检查异常(Checked Exception)和不受检查异常(Unchecked Exception)。

受检查异常是在我们写代码的时候必须处理的,不然编译就无法通过,现代的IDE都会检测并提示给我们。比如说在读写文件的时候可能会出现文件不存在这种情况,那么就要对这种情况进行处理,Java对读的文件可能不存在的这种情况,是通过强制我们处理FileNotFoundException异常来完成的。在我们代码中需要捕获这个异常然后做相关处理。

不受检查异常通常也称作运行时异常,Java中所有的这种异常都继承自RuntimeException类,运行型异常顾名思义就是运行时才会抛出的异常。这种异常是无法预先知晓的,只有运行时候才会检测出来,比如说很常见的NullPointerException异常,它是由于我们处理的对象为null导致的。对象是否真正为null,只有运行时才能确定。

我们自己写线程的时候,同样也就涉及到异常的处理,下面来看看关于线程异常处理的问题。

Java中线程通过Thread类来表示,而真正任务的执行l逻辑在Runnable接口的run方法中定义,来看一下Runnable接口的定义:

java.lang.Runnable

public interface Runnable {
    public abstract void run();
}

实现Runnable来定义任务执行逻辑,如下:

public class Task implements Runnable {
    @Override
    public void run() {
      // do something
    }
}

启动一个线程执行任务,如下:

public static void main(String[] args) {
   Task task = new Task();
   Thread t1 = new Thread(task);
   t.start();
}

t1线程通过主线程启动,然后执行任务。

现在我们回到我们本篇文章要讨论的内容:线程异常处理。也就是处理线程执行的异常,而线程真正的执行逻辑实际上就是Runnable接口中定义的run方法的执行,这个方法执行可能会出现异常,我们要对它进行处理。

上面我们说过代码的执行可能会出现两种异常:受检查异常和不受检查异常。那么在线程运行的时候如何处理呢?

受检查异常处理

受检查异常有两种处理方式:通过try...catch...捕获处理或者通过throw抛出有上层代码处理。

try...catch...处理

@Override
public void run() {
    try {
        // Code where an exception will occur
    } catch (Exception ex) {
        // do something
    }
}

throw抛出

这种方式不适用于受检查异常的处理,因为Runnable接口定义的run方法不能抛出受检查异常。

在java.lang.Callable接口中,在比较Callable和Runnable的不同的时候有这样的说明:

A {@code Runnable}, however, does not return a result and cannot throw a checked exception.

疑问:这里只说了不能抛出受检查异常,那么是否能抛出不受检查异常给当前线程的启动线程呢?后面作说明。

不受检查异常处理

关于不受检查异常,《Java编程思想》中有这样的一段描述:

属于运行时异常的类型有很多,它们会自动被Java虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从RuntimeException类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出RuntimeException类型的异常(或者任何从RuntimeException继承的异常),它们被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查RuntimeException的话,代码就显得太混乱了。不过尽管通常不用捕获RuntimeException异常,但还是可以在代码中抛出RuntimeException类型的异常。

的确如上述代码所说,有时候我们确实会在代码中抛出RuntimeException异常,作为与其他类或方法的一种交流方式。那么如此所说,在Runnable的run方法中我们也可以这样操作:

@Override
public void run() {
    throw new RuntimeException(“我抛出了一个运行时异常!”);
}

当你选择在代码中主动抛出RuntimeException的时候,这时候你的目的我想是为了把当前线程执行的异常抛出交给上层代码处理吧!那么现在我们的问题就来了,上层代码能捕获到你抛出的这个RuntimeException吗?看下面的测试代码:

/**
 * @Author rocky.hu
 * @Date 10/17/2019 10:33 AM
 */

public class Task implements Runnable {

    @Override
    public void run() {
        throw new RuntimeException("我抛出了一个运行时异常A!");
    }

    public static void main(String[] args) {
        try {
            Task task = new Task();
            Thread t = new Thread(task);
            t.start();
            System.out.println("你好啊!");
        } catch (RuntimeException e) {
            System.out.println("Main捕获到A了。");
        }

    }

}

运行代码,结果如下:

你好啊!
Exception in thread "Thread-0" java.lang.RuntimeException: 我抛出了一个运行时异常A!
    at org.wizard.etcd.Task.run(Task.java:14)
    at java.lang.Thread.run(Thread.java:748)

从代码可以看出,main主线程并没有捕获线程t执行时抛出的RuntimeException异常。也就是说通过throw RuntimeException以交给当前线程启动线程处理的这种方式是无效的。

上面是我们主动抛出RuntimeException,同样的Java虚拟机自动抛出的RuntimeException也是无法在上层空间被捕获到。

public class Task implements Runnable {

    @Override
    public void run() {
        String s = null;
        s.length();
    }

    public static void main(String[] args) {
        try {

            Task task = new Task();
            Thread t = new Thread(task);
            t.start();
            System.out.println("你好啊!");
        } catch (RuntimeException e) {
            System.out.println("Main捕获到运行时异常。");
        }

    }

}

运行代码,结果如下:

你好啊!
Exception in thread "Thread-0" java.lang.NullPointerException
    at org.wizard.etcd.Task.run(Task.java:13)
    at java.lang.Thread.run(Thread.java:748)

那么如果们确定是想捕获线程的运行时异常,应该怎么办?JDK1.5提供了一个接口:java.lang.Thread.UncaughtExceptionHandler。通过这个接口来处理线程的运行时异常,这个从它的名称也可以看出来它的作用。此接口的定义如下:

java.lang.Thread.UncaughtExceptionHandler

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

使用示例如下:

/**
 * @Author rocky.hu
 * @Date 10/17/2019 10:33 AM
 */

public class Task implements Runnable {

    @Override
    public void run() {
       throw new RuntimeException("我抛出一个运行时异常!");
    }

    public static void main(String[] args) {
        try {

            Task task = new Task();
            Thread t = new Thread(task);
            t.setUncaughtExceptionHandler(new MyUnCaughtExceptionHandler());
            t.start();
            System.out.println("你好啊!");
        } catch (RuntimeException e) {
            System.out.println("Main捕获到运行时异常。");
        }

    }

}

class MyUnCaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("不受检查异常捕获器:" + e);
    }
}

运行结果如下:

你好啊!
不受检查异常捕获器:java.lang.RuntimeException: 我抛出一个运行时异常!
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容

  • 八、深入理解java异常处理机制 引子try…catch…finally恐怕是大家再熟悉不过的语句了, 你的答案是...
    壹点零阅读 1,562评论 0 0
  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,538评论 0 6
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,789评论 0 10
  • 任务和线程的启动很容易。 在大多数时候, 我们都会让它们运行直到结束,或者让它们自行停止。然而,有时候我们希望提前...
    好好学习Sun阅读 1,151评论 0 0
  • 贺瑾焦点解决网络中级十期 坚持分享第204天 2019.1.14 面对孩子的不良行为,你能做到5遍都不怒吗? ...
    贺瑾阅读 155评论 0 2