java 线程的运行时异常处理-UncaughtExceptionHandler

Java 线程(Runnable)的异常处理, 以下的阐述均基于Runnable的线程实现

首先,有个问题,如果Java子线程发生异常会怎么样?实际上会导致该线程直接终止。当年自己写了个线程模型大概是这样的:

1
2
3
Spring容器启动 (1)
-> 主线程中启动一个子线程T,在while循环中去接收socket请求 (2)
-> 收到一个请求,submit一个线程 (3)

当时我一只担心步骤2的线程T会因为什么而挂掉,然后大家没办法上传文件。而当时完全不知道UncaughtExceptionHandler的存在,多希望有一丁点指导啊。

那么如何处理可能出现的异常而不至于线程直接挂掉呢?
第一,把整个run中的异常在内部捕获掉
第二,直接在线程中setUncaughtExceptionHandler,将异常处理与run分开。

其实,有的同学可能疑惑,为什么不能直接把整个线程用try-catch包裹起来。因为这本来就没有什么用,譬如:

1
2
3
4
5
6
7
// 普通线程即使使用try...catch也无法捕获到抛出的异常
try {
Thread t = new Thread();
t.start();
} catch (Exception e) {
System.out.println("catch RunTimeException "); // 不起作用
}

假设t是从线程x中 new 出来的,那么x称为t的父线程。而线程与线程间的运行本是异步的(当然如果不使用线程间通信机制的情况下),有可能x在t还没结束时先结束,这是有可能的,如果x不是主线程的话。 所以,try-catch在异步的线程中捕获是没有意义的。
正确的方法是:

1
2
3
4
5
6
7
8
9
new Thread(new Runnable() {
@Override
public void run() {
try{//wrap all code
Integer.parseInt("sfasf");
System.out.println(0);
}catch(...){}
}
}).start();

或者使用UncaughtExceptionHandler 捕获线程运行体的运行时异常,使用也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 继承UncaughtExceptionHandler 接口
*/
class ErrHandler implements UncaughtExceptionHandler
{
public void uncaughtException(Thread t, Throwable e)
{
System.out.println("This is:" + t.getName() + ",Message:" + e.getMessage());
e.printStackTrace();
}
}

在线程中使用:

1
2
3
4
5
6
7
8
9
10
11
12
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(ErrHandler);
//Thread.currentThread().setUncaughtExceptionHandler((t, e) -> {
// System.out.println("current thread is " + t.getName() + " " + e.getMessage());
//});
Integer.parseInt("sfasf");
System.out.println(0);
}
}).start();

如果没有捕获异常,不会输出0,而有了handler则不影响下面的执行,会正常输出了。
或者是:

1
2
Thread a = new Thread();
a.setUncaughtExceptionHandler(ErrHandler);

正如上面所写,在java 8中还可以用lamda表达式简化语法,用匿名handler代替。不过使用a.setUEH(handler)这种方式是策略模式的体现,更容易在需要的时候随时替换handler的实现,正如sort函数的Comparator参数一样。