Java多线程编程(4)Unchecked异常、Hook线程

Unchecked 异常

什么是 unchecked 异常

在 Java 中有形形色色的异常。如果要长篇大论的话,那么就如下段:

Throwable 包括 Error 和 Exception。
Error 包括 LinkeageError、VirtualMachineError ...
Exception 包括 RuntimeException、IOException、AWTException ...

这里面我们常见的是 Exception,大多数 Exception 是编译器可以发现的,这种叫做 Checked 异常。在 Java 入门时介绍过try ... exception() ...这样的语法了。

但是 RuntimeException 并不能在编译时被发现。要运行起来后才能被发现,最常见的是下标溢出、空指针。
RuntimeException 和 Error,统称 Unchecked 异常。

常规的方法无法捕捉

我们写下面一段代码,内容有除以0,会抛出一个 Runtime 异常。

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.print(10/5);
            System.out.print(10/0);
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }

如果就这样允许的话,我们可以在终端看到异常

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero

我们试试用之前的try ... catch来捕获。

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.print(10/5);
            System.out.print(10/0);
        };
        try {
            Thread thread = new Thread(runnable);
            thread.start();
        } catch (Exception e) {
            System.out.print("捕获到了异常"); // 并不会输出这一句
        }
    }

其结果是,还是和刚才一样,输出了异常,而不是输出我们设定的字符串。这说明,线程中的Runtime 异常并不会被普通的try ... catch ...捕获。

Thread的run方法是不抛出任何检查型异常的,但是它自身却可能因为一个异常而被中止,导致这个线程的终结。线程不可以直接抛出异常,当出现 Unchecked 异常时,需要回调 UncaughtExceptionHandler 接口。

UncaughtExceptionHandler 接口

我们可以用 Thread对象的setUncaughtExceptionHandler()来设置一个异常处理器。

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.print(10/5);
            System.out.print(10/0);
        };
        Thread thread = new Thread(runnable);
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + " 线程中发生了 " + e.toString());
            }
        });
        thread.start();
    }

输出

2Thread-0 线程中发生了 java.lang.ArithmeticException: / by zero

这里也可以把代码优化为 lambda 表达式。

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.print(10/5);
            System.out.print(10/0);
        };
        Thread thread = new Thread(runnable);
        thread.setUncaughtExceptionHandler((t, e) -> System.out.println(t.getName() + " 线程中发生了 " + e.toString()));
        thread.start();
    }

Android 系统中的例子

/**
     * Handle application death from an uncaught exception.  The framework
     * catches these for the main threads, so this should only matter for
     * threads created by applications. Before this method runs, the given
     * instance of {@link LoggingHandler} should already have logged details
     * (and if not it is run first).
     */
    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;

        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try { 
                // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
                if (mCrashing) return;
                   mCrashing = true;

                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }
                // step1
                // Bring up crash dialog, wait for it to be dismissed
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                    // System process is dead; ignore
                } else {
                    try {
                        // step2
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                        // Even Clog_e() fails!  Oh well.
                    }
                }
            } finally {
                // step 3
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
    }

第一步,弹出crash的对话框;

第二步:crash日志输出;

第三步,在finally 里面杀掉进程退出。

Hook 线程

印象里在初中学 Visual Basic 时有什么“程序退出时”的事件。在这样的事件中,写一些“善后”的代码,比如保存游戏进度等。

在 Java 里,这样的功能由 Hook 线程实现。

使用Runtime.getRuntime().addShutdownHook()来添加一个 hook 线程,在进程即将退出时,会运行这些 hook 线程。

触发条件

笼统地讲,就是“JVM 进程即将退出时”。仔细地讲,则包括以下两种情况

  1. 没有 active 的非守护线程;

  2. 中断;

满足二者任意之一即可。

多个 Hook 线程

可以设置多个 Hook 线程。会存放在一个映射IdentityHashMap<Thread, Thread> hooks之中。这种数据结构判断 key 是否相同是并不是使用equals方法,而是看引用的是否是同一对象。

当触发条件、进程即将退出时,会运行这个 Map 中所有的线程。

简单示例

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hook 线程被调用");
            }
        }));
        // 装模作样做一些计算
        int a = 1;
        int b = 1;
        for (int i = 0; i < 10; i++) {
            int temp = a + b;
            a = b;
            b = temp;
            System.out.println(a + ", " + b);
        }
    }

代码中装模作样地计算斐波那契数列,在计算完毕后,即将退出的时候,才会输出“Hook 线程被调用”。

可以看到,我这里的 Hook 线程虽然写在了装模作样计算的前面,但是实际上是退出时被调用,输出的内容在最后面。

应用场景

释放资源

那自然是释放掉文件读写的锁、数据库的连接等资源。

防止进程重复启动

在硬盘上存一个 lock 文件。

进程启动时,判断一下 lock 文件在不在,在的话就不启动;不在的话就顺利启动,但是创建 lock 文件。

进程退出时,删除掉这个 lock 文件,这个时候就需要 Hook 线程了。

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

推荐阅读更多精彩内容