CP-06-线程未捕获异常处理器

TreadGroup

首先提一个概念:线程组,它可以批量管理线程或线程组对象,有效地对线程或线程组对象进行组织。

可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。

注意区分线程池:线程池是一堆new好的线程对象。

线程未捕获异常处理器

线程执行完run方法即可正常退出,如果发生了没有捕获的异常则会异常退出。大多数情况下,由于异常导致的线程退出都不是我们想要的。所以在编写的代码的时候要尽可能的捕获处理可以处理的异常,但是也不能光简单的捕获异常然后什么也不做。

下面介绍线程异常的处理办法。

为单个线程指定未捕获异常处理器

JVM为我们提供了线程的未捕获异常处理机制,通过Thread的setUncaughtExceptionHandler方法:

1
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)

可以设置当前线程的未捕获异常处理器。如下面的例子就通过设置uncaughtExceptionHandler成功捕获到了除0异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) {

Thread t = new Thread(new Runnable(){
public void run() {
System.out.println("线程run.....");
int i = 1/0;
}
});

t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {

public void uncaughtException(Thread t, Throwable e) {
System.out.println("uncaughtExceptionHandler catch a exception");
System.out.println(e.getMessage());
}
});
t.start();


try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}

//结果:
线程run.....
uncaughtExceptionHandler catch a exception
/ by zero

线程出现未捕获异常后,JVM将调用Thread中的dispatchUncaughtException方法把异常传递给线程的未捕获异常处理器。

1
2
3
4
5
6
7
8
9
10
11
12
/** 
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}

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

方法的描述已经清楚地说明了这个方法只是提供给JVM调用的,getUncaughtExceptionHandler方法并没有简单返回设置好的uncaughtExceptionHandler:

可见,如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。ThreadGroup类定义:

1
class ThreadGroup implements Thread.UncaughtExceptionHandler  

ThreadGroup实现的uncaughtException如下:

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

为所有线程设置默认的未捕获异常处理器

默认情况下,线程组处理未捕获异常的逻辑是,首先将异常消息通知给父线程组,然后尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常,如果没有默认的异常处理器则将错误信息输出到System.err。也就是JVM提供给我们设置每个线程的具体的未捕获异常处理器,也提供了设置默认异常处理器的方法。

设置了默认的异常处理器后,系统中所有未直接设置异常处理器的线程将使用这个默认的异常处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static void main(String[] args) {

Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {

public void uncaughtException(Thread t, Throwable e) {
System.out.println("default uncaughtExceptionHandler catch a exception "
+ "from thread name= " + Thread.currentThread().getName() + " ,"
+ " threadGourp= " + Thread.currentThread().getThreadGroup().getName() );
System.out.println(e.getMessage());
}
});

ThreadGroup threadGroup = new ThreadGroup("fuyi");
Thread t1 = new Thread(threadGroup, new Runnable(){
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "run.....");
int i = 1/0;
}
}, "t1");

Thread t2 = new Thread(threadGroup, new Runnable(){
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "run.....");
int i = 1/0;
}
}, "t2");

t1.start();
t2.start();


try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}

//结果
线程t2run.....
线程t1run.....
default uncaughtExceptionHandler catch a exception from thread name= t2 , threadGourp= fuyi
/ by zero
default uncaughtExceptionHandler catch a exception from thread name= t1 , threadGourp= fuyi
/ by zero

虚拟机执行main方法的线程属于一个名字叫做“main”的线程组。在应用程序中,创建一个线程的时候如果没有重新制定线程组,则会继承这个“main”线程组。也就是说所有的线程都会有线程组,即使我们在构造线程时显示的传入null值的线程组,最终JVM也会为我们分配到一个线程组。Thread的init方法决定了这个特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (g == null) {  
/* Determine if it's an applet or not */

/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}

/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}

参考:http://blog.csdn.net/hongxingxiaonan/article/details/50527169