提到线程的中断在某些情况下会抛出InterruptedException异常,最终导致线程的终止。其实,线程也有可能由于其他异常原因造成终止,在某些情况下为了做一些妥善的处理,我们需要捕获这些异常情况。看下面代码,觉得会怎样?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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也实现了这个接口。按照文档注释说明:
- 优先检查线程的uncaughtExceptionHandler
- 否则交给group处理
- 最后才可能轮到defaultUncaughtExceptionHandler
而看源码中线程组的处理实际上是追溯一个parent(也是ThreadGroup类对象)链:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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()还是优雅的选择。
到此,对线程相关的基本介绍先告一段落,读者们觉得需要补充的地方,我真得是非常欢迎补充、拍砖和指正的,对你我的学习成长都有很大好处。:)