之前写过几个线程,线程A不停的扫描(while (true))指定目录下有没有新的binlog日志文件产生,线程B每晚执行定时任务,删除三天前产生的日志文件。当线程A通过类File读取指定目录和文件时,线程B刚好执行删除任务,线程A会抛出IOException, 导致线程A逻辑出错,结果线程A不是一直扫描(RUNNING),而是一直停留在WAIT状态。现在,我们就简单讨论一下,子线程抛出异常如何处理。

首先明确线程代码的边界。其实很简单,Runnable接口的run方法所界定的边界就可以看作是线程代码的边界。Runnable接口中run方法原型如下:

public void run();

而所有的具体线程都实现这个方法,所以这里就明确了一点,线程代码不能抛出任何checked异常。所有的线程中的checked异常都只能被线程本身消化掉。:) 这样本身也是符合线程的设计理念的,线程本身就是被看作独立的执行片断,它应该对自己负责,所以由它来消化所有的checked异常是很正常的。因此,我们首先确定:checked异常一定要在线程内部消化,最常用的方式,就是try{...}catch(..){...}

但是,线程代码中是可以抛出错误(Error)和运行级别异常(RuntimeException)的。Error俺们可以忽略,因为通常Error是应该留给jvm的,而RuntimeException确是比较正常的,如果在运行过程中满足了某种条件导致线程必须中断,可以选择使用抛出运行级别异常来处理,如下:

public void run() {
    if (...) throw new RuntimeException();
}

当线程代码抛出运行级别异常之后,线程会中断。:)这点java中解释得很清楚:
@see Thread
All threads that are not daemon threads have died, either by returning from the call to the run method or “by throwing an exception that propagates beyond the run method”.
但是对于invoke此线程的主线程会产生什么影响呢?主线程不受这个影响,不会处理这个RuntimeException,而且根本不能catch到这个异常。会继续执行自己的代码 :)
所以得到结论:线程方法的异常只能自己来处理。

但是,给某个thread设置一个UncaughtExceptionHandler,可以确保在该线程出现异常时能通过回调UncaughtExceptionHandler接口的public void uncaughtException(Thread t, Throwable e) 方法来处理异常,这样的好处或者说目的是可以在线程代码边界之外(Thread的run()方法之外),有一个地方能处理未捕获异常。但是要特别明确的是:虽然是在回调方法中处理异常,但这个回调方法在执行时依然还在抛出异常的这个线程中!另外还要特别说明一点:如果线程是通过线程池创建,线程异常发生时UncaughtExceptionHandler接口不一定会立即回调。代码示例如下:

package study20170103;

/**
 * Created by apple on 17/1/3.
 */

public class ThreadTest extends ThreadGroup{
    private ThreadTest(){
        super("ThreadTest");
    }

    public static void main(String[] args) {
        new Thread(new ThreadTest(),new Runnable() {//传入继承ThreadGroup的类对象
            @Override
            public void run() {
                throw new NullPointerException();//只能抛出unchecked异常
            }
        }).start();
    }

    public void uncaughtException(Thread thread, Throwable exception) {
        /**
         * 当线程抛出unckecked异常时,系统会自动调用该函数,但是是在抛出异常的线程内执行
         */
        System.out.println(thread.getId());
        exception.printStackTrace();//example,   print stacktrace
    }

}

比之上述方法,还有一种编程上的处理方式可以借鉴,即,有时候主线程的调用方可能只是想知道子线程执行过程中发生过哪些异常,而不一定会处理或是立即处理,那么发起子线程的方法可以把子线程抛出的异常实例收集起来作为一个Exception的List返回给调用方,由调用方来根据异常情况决定如何应对。不过要特别注意的是,此时子线程早以终结。

线程设计的理念:“线程的问题应该线程自己本身来解决,而不要委托到外部。”