UncaughtExceptionHandler—处理Runnable线程内的非受检异常

来源:互联网 发布:解压缩for mac 编辑:程序博客网 时间:2024/05/18 02:51

学过Java多线程的伙伴们都知道,Java入门的多线程方案就是Thread类和Runnable类。如下:


public class Demo {    public static void main(String[] args) {        fun1();        fun2();    }    public static void fun1() {        Thread thread = new Thread() {            @Override            public void run() {                System.out.println("fun1-thread");            }        };        thread.start();    }    public static void fun2() {        Thread thread = new Thread() {            @Override            public void run() {                System.out.println("fun2-thread");            }        };        thread.start();    }}

这是Java的基础内容,在此不再赘述。不知道的伙伴可以参见我之前的博客,今天我们要说的是线程内部的异常处理。

Java的异常非为受检异常和非受检异常。由于在多线程中,run()方法无法继续向上显式抛出异常。所以这就使得Thread线程中的异常处理变得棘手。

首先对于受检异常,其应对办法非常通用:就是直接在run()方法体中捕获异常,然后进行对应的处理。对于非受检异常,JVM会帮助我们捕获到。那么我们如何处理JVM捕获的异常呢?答案是Thread.UncaughtExceptionHandler类。正如JDK文档所介绍的一样:

当一个线程由于发生了非受检异常而终止时,JVM会使用Thread.gerUncaughtExceptionHandler()方法查看该线程上的UncaughtExceptionHandler,并调用他的uncaughtException()方法”——翻译自JDK7 API文档。

下面我们来尝试使用hread.UncaughtExceptionHandler类,来处理线程内部的非受检异常(受检异常直接在run()方法体内部的catch子句中处理)。

import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;/** * Created by yizhen on 2017/1/7. */public class Demo {    private static final AtomicInteger num = new AtomicInteger();    public static void main(String[] args) throws InterruptedException {        final Thread thread = new Thread(new ThreadTask());        //此处使用thread.setUncaughtExceptionHandler(new ThreadExceptionHandler)可以代替ThreadTask中        //的Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler(),但这样不好        thread.start();    }    private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {        @Override        public void uncaughtException(Thread t, Throwable e) {            System.out.println("One uncaught exception was got:");            System.out.println("Thread id=" + t.getId());            System.out.println("Thread name=" + t.getName());            e.printStackTrace(System.out);            // 当捕获到非受检异常时,可以断定原来的线程已经被JVM终止。            // 此时可以新建一个线程继续执行原来的任务            if (num.get() < 5) {                new Thread(new ThreadTask()).start();            }        }    }    private static final class ThreadTask implements Runnable {        @Override        public void run() {            num.incrementAndGet();            Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());//设置非受检异常的ExceptionHandler            try {                for (int i = 4; i >= 0; i--) {                    TimeUnit.SECONDS.sleep(1);                    // 当i=0时候抛出的非受检异常,将导致当前线程被JVM杀死                    // 但异常会被在上面设置的ThreadExceptionHandler捕获到,进而被处理                    System.out.println(12 / i);                }            } catch (InterruptedException e) {//受检异常直接在run方法体内部处理                e.printStackTrace();            }        }    }}


上面的代码不难理解,在主函数中,我们执行了一个Thread线程,这个线程中执行ThreadTask任务。在ThreadTask任务的实现中,如果是受检异常,直接在run()方法体中处理。对于非受检异常,我们在run()方法体的开头设置了非受检异常处理类ThreadExceptionException,这个类的uncaughtException()方法就是处理线程内部非捕获异常的具体执行者。该方法会判断任务执行的次数,如果没有超过5次,就会创建一个Thread线程来重新执行任务(读者要清楚,一旦一个线程抛出了非受检异常,JVM就会把它杀死,然后把捕获到的非受检异常传递给UncaughtExceptionHandler类对象类处理)
一定有读者会问,如果没有通过setUncaughtExceptionHandler()方法设置Handler怎么办?这个问题在JDK API中给了回答:
“如果一个线程没有显式的设置它的UncaughtExceptionHandler,JVM就会检查该线程所在的线程组是否设置了UncaughtExceptionHandler,如果已经设置,就是用该UncaughtExceptionHandler;否则查看是否在Thread层面通过静态方法setDefaultUncaughtExceptionHandler()设置了UncaughtExceptionHandler,如果已经设置就是用该UncaughtExceptionHandler;如果上述都没有找到,JVM会在对应的console中打印异常的堆栈信息。”——翻译自JDK7 API文档
/** * Created by yizhen on 2017/1/7. */public class Demo {    public static void main(String[] args) throws InterruptedException {        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {            @Override            public void uncaughtException(Thread t, Throwable e) {                System.out.println("static thread exception handler -- " + t.getName());            }        });        final Thread t1 = new Thread(new ThreadTaskWithHandler(), "t1");        t1.start();        final Thread t2 = new Thread(new ThreadTaskNoHandler(), "t2");        t2.start();    }    private static final class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler {        @Override        public void uncaughtException(Thread t, Throwable e) {            System.out.println("explicit exception handler -- " + t.getName());        }    }    private static final class ThreadTaskWithHandler implements Runnable {        @Override        public void run() {            Thread.currentThread().setUncaughtExceptionHandler(new ThreadExceptionHandler());            System.out.println(12 / 0);        }    }    private static final class ThreadTaskNoHandler implements Runnable {        @Override        public void run() {            System.out.println(12 / 0);        }    }}
从上面的程序可以看到,t1线程的非受检异常始终会被explicit exception handler捕获到,而t2线程的非受检异常始终会被static thread exception handler捕获到。
至此,Thread线程内的异常处理就介绍完了,这包括受检异常和非受检异常。细心的读者会发现,本文仅仅涉及Thread和Runnable的多线程体系。在Java中,还有Executor和Callable的多线程体系。那关于这个体系的异常如何处理呢?笔者会后续博文中介绍!
0 0
原创粉丝点击