线程、Thread类和线程终止相关整理(下)——线程异常&JVM停止

接着前文的Thread类简介和线程终止/任务取消以及中断的相关内容,本篇对线程相关的异常处理和Java虚拟机(JVM)的停止,尤其是shutdownHook相关内容做整理,作本系列的一个小结。

上篇讲中断的时候,提到线程的中断在某些情况下会抛出InterruptedException异常,最终导致线程的终止。其实,线程也有可能由于其他异常原因造成终止,在某些情况下为了做一些妥善的处理,我们需要捕获这些异常情况。看下面代码,觉得会怎样?

public class App {
	static class ExRun implements Runnable {
		public void run() {
			System.out.println("throwing...");
			throw new RuntimeException();
		}
	}

	public static void main(String[] args) {
		try {
			new Thread(new ExRun()).start();
		} catch (Exception e) {
			System.out.println("runtime ex. catched!");
		}
	}
}

会在控制台打出”runtime ex. catched!”?你确定么?不怕大家笑话,几年我第一眼看类似这段代码的时候,竟然也认为这很理所当然。其实,这也许不是年头的问题,是是否仔细思考和经验问题。只要仔细想一想,既然已经new了一个Thread并且让他start()去了,异常又不是在new的过程中或者start的过程中出来的,抛不抛异常关这个主线程什么事儿。

那如果一个被主线程创建的新线程除了什么“故障”,就让其悄悄地自生自灭么?有没有补救方案呢?有的。在JavaSE5之前,这个问题直接交给线程组(ThreadGroup)了事儿,但在这之后就有了java.lang.Thread$UncaughtExceptionHandler($是内部关系,后者是前者的内部类/接口)这么一个interface,里面只有一个方法:

void uncaughtException(Thread t, Throwable e);

这个方法就是负责处理异常的,Thread参数不说了,那这个Throwable又是啥,这个就是Exception的老爸了,不了解的异常类层次结构的,这儿您就可以查查去了。既然是Throwable,其实也就不只是Exception了,Error也会被处理到。

接着就简单说下怎么用,Thread类的两个方法:

  • public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
  • public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

第一个是实例方法,需要针对某个Thread对象使用,而第二个是静态方法,设置全局的默认处理方法,但这两个不冲突,在Thread中是两个独立的handler属性。但毕竟默认的和非默认的还不一样,有个顺序问题,这里又不得不再提到线程组(ThreadGroup)

在JavaSE5之前,没有UncaughtExceptionHandler这个东西,所以出错会直接交给Thread的ThreadGroup对象属性group处理,因为ThreadGroup有处理异常的方法,而UncaughtExceptionHandler出来后,ThreadGroup也实现了这个接口。按照文档注释说明:

  1. 优先检查线程的uncaughtExceptionHandler
  2. 否则交给group处理
  3. 最后才可能轮到defaultUncaughtExceptionHandler

而看源码中线程组的处理实际上是追溯一个parent(也是ThreadGroup类对象)链:

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

最后为空时,采用全局默认的的handler。

线程异常就说道这,已经说了不少了,下面简单说两句JVM的退出/停止。

通常我们在课本上都会知道java.lang.System类有个方法是exit(int),这个会让程序退出,而实际上这个方法实际上是java.lang.Runtime类exit(int)的封装(其实System还有像gc()等很多方法,实际执行者都是这个java.lang.Runtime类对象),意义是告诉当前运行环境关闭运行,也就通常被理解为JVM“关闭”。

说下Runtime类,这个类是个单例模式的类,即从头到尾程序只能获得同一个对象引用,即调用静态方法Runtime.getRuntime()所得。Runtime类负责和运行时环境相关的各种任务,有很多有趣的方法,比如很多应用(如Tomcat)中某些线程池中线程数目的确定和当前运行环境处理器个数有关,则可通过Runtime的availableProcessors()方法获得。这里重点说下exit()方法。

还有一个概念需要说下,就是ShutdownHook,这个听起来“高端洋气上档次”的东西实际上就是一个Thread,但它的特殊就在于是一个hook,在JVM关闭(exit)的时候才会执行到。就如线程和中断的关系一样,JVM和exit()也是一样,exit()的执行并不是虚拟机马上关掉,而是安排结束时所要执行任务,按文档说明分上下两个过程,前面的过程就是shutdown对应Thread执行的过程。要想让虚拟机优雅的结束,exit()安排执行shutdownHook清理环境是必要的,还说Tomcat吧,终止的时候也会执行注册过的hook。执行hook的时候,通常守护/非守护线程也都仍然可以执行,但对hookd的新增和移除则不可以了。

Runtime和shutdonwHook关联起来的方法,主要就是这俩了:

  • public void addShutdownHook(Thread hook)
  • public boolean removeShutdownHook(Thread hook)

具体用法不用多说估计看文档都能懂,需要注意的地方就是,想要remove必需在add的时候留有hook的Thread对象引用。

和Thread的stop()类似,Runtime也有halt()方法,但这是一个“暴力停机”的方法,不会考虑hook的执行,如果情况允许,exit()还是优雅的选择。

到此,对线程相关的基本介绍先告一段落,读者们觉得需要补充的地方,我真得是非常欢迎补充、拍砖和指正的,对你我的学习成长都有很大好处。:)

此条目发表在 Java, Java语言, 并发, 开发, 计算机技术 分类目录,贴了 , , , , , , , , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>