背景
上篇分析了Thread的相关信息你以为的线程就是你以为的线程么
本次分析一下异常handler【想来写Android的开发者对这个接口比较熟悉】
/** * Interface for handlers invoked when a <tt>Thread</tt> abruptly * terminates due to an uncaught exception. * <p>When a thread is about to terminate due to an uncaught exception * the Java Virtual Machine will query the thread for its * <tt>UncaughtExceptionHandler</tt> using * {@link #getUncaughtExceptionHandler} and will invoke the handler's * <tt>uncaughtException</tt> method, passing the thread and the * exception as arguments. * If a thread has not had its <tt>UncaughtExceptionHandler</tt> * explicitly set, then its <tt>ThreadGroup</tt> object acts as its * <tt>UncaughtExceptionHandler</tt>. If the <tt>ThreadGroup</tt> object * has no * special requirements for dealing with the exception, it can forward * the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler * default uncaught exception handler}. * * @see #setDefaultUncaughtExceptionHandler * @see #setUncaughtExceptionHandler * @see ThreadGroup#uncaughtException * @since 1.5 */ public interface UncaughtExceptionHandler { /** * Method invoked when the given thread terminates due to the * given uncaught exception. * <p>Any exception thrown by this method will be ignored by the * Java Virtual Machine. * @param t the thread * @param e the exception */ void uncaughtException(Thread t, Throwable e); }
默认来看ThreadGroup就是UncaughtExceptionHandler
基础
static class ExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("==Exception: " + e.getMessage()); } } static class MyThread implements Runnable { @Override public void run() { System.out.println(10 / 2); System.out.println(10 / 0); System.out.println(10 / 1); } } @Test public void testThread() { Thread myThread = new Thread(new MyThread()); myThread.start(); } @Test public void testExceptionThread() { Thread myThread = new Thread(new MyThread()); myThread.setUncaughtExceptionHandler(new ExceptionHandler()); myThread.start(); } @Test public void testNoThread() { new MyThread().run(); }
这边存在三种使用方式
- testThread 使用线程 线程中抛出异常
-
testExceptionThread 使用线程 线程中抛出异常 同时使用UncaughtExceptionHandler
-
testNoThread 直接使用run方法(上一篇分析过 runnable的run方法就是普通方法)
那么三种不同的方式分别调用的结果如下



其实也很明显了我们在线程中的异常时无法在启动线程中直接捕捉的
设想如果使用线程池出现异常 那么线程时直接中断了 那么有无必要重启新的线程保证有业务线程在执行呢?
使用大量的第三方库 当一个不正确的使用或潜在的bug出现了没有捕获异常导致业务线程中断了?
Android开发碰到一个异常信息在客户端崩溃了,开发者不能获得有效信息?
UncaughtExceptionHandler的出现给我们提供了一个思路
类图

当一个异常发生了,如果线程内部没有捕获那么该异常会交由该线程的UncaughtExceptionHandler来处理
将会调用UncaughtExceptionHandler的uncaughtException方法
上篇分析我们默认情况下线程都会存在一个ThreadGroup,而ThreadGroup从类图得知就是一个UncaughtExceptionHandler
uncaughtException
/** * Called by the Java Virtual Machine when a thread in this * thread group stops because of an uncaught exception, and the thread * does not have a specific {@link Thread.UncaughtExceptionHandler} * installed. * <p> * The <code>uncaughtException</code> method of * <code>ThreadGroup</code> does the following: * <ul> * <li>If this thread group has a parent thread group, the * <code>uncaughtException</code> method of that parent is called * with the same two arguments. * <li>Otherwise, this method checks to see if there is a * {@linkplain Thread#getDefaultUncaughtExceptionHandler default * uncaught exception handler} installed, and if so, its * <code>uncaughtException</code> method is called with the same * two arguments. * <li>Otherwise, this method determines if the <code>Throwable</code> * argument is an instance of {@link ThreadDeath}. If so, nothing * special is done. Otherwise, a message containing the * thread's name, as returned from the thread's {@link * Thread#getName getName} method, and a stack backtrace, * using the <code>Throwable</code>'s {@link * Throwable#printStackTrace printStackTrace} method, is * printed to the {@linkplain System#err standard error stream}. * </ul> * <p> * Applications can override this method in subclasses of * <code>ThreadGroup</code> to provide alternative handling of * uncaught exceptions. * * @param t the thread that is about to exit. * @param e the uncaught exception. * @since JDK1.0 */ 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); } } }
从默认情况下ThreadGroup的uncaughtException会被jvm调用(当线程由于异常终止并且没有默认的UncaughtExceptionHandler)
此时很明显会打出我们如上图Junit测试那般

如果ThreadGroup的parent不为空将会一直传递到parent的顶层【就是下文所说的system】然后报错【打印线程名和堆栈】
构造函数
我们来查看一下ThreadGroup的构造函数
/** * Creates an empty Thread group that is not in any Thread group. * This method is used to create the system Thread group. */ private ThreadGroup() { // called from C code this.name = "system"; this.maxPriority = Thread.MAX_PRIORITY; this.parent = null; } /** * Constructs a new thread group. The parent of this new group is * the thread group of the currently running thread. * <p> * The <code>checkAccess</code> method of the parent thread group is * called with no arguments; this may result in a security exception. * * @param name the name of the new thread group. * @exception SecurityException if the current thread cannot create a * thread in the specified thread group. * @see java.lang.ThreadGroup#checkAccess() * @since JDK1.0 */ public ThreadGroup(String name) { this(Thread.currentThread().getThreadGroup(), name); } /** * Creates a new thread group. The parent of this new group is the * specified thread group. * <p> * The <code>checkAccess</code> method of the parent thread group is * called with no arguments; this may result in a security exception. * * @param parent the parent thread group. * @param name the name of the new thread group. * @exception NullPointerException if the thread group argument is * <code>null</code>. * @exception SecurityException if the current thread cannot create a * thread in the specified thread group. * @see java.lang.SecurityException * @see java.lang.ThreadGroup#checkAccess() * @since JDK1.0 */ public ThreadGroup(ThreadGroup parent, String name) { this(checkParentAccess(parent), parent, name); } private ThreadGroup(Void unused, ThreadGroup parent, String name) { this.name = name; this.maxPriority = parent.maxPriority; this.daemon = parent.daemon; this.vmAllowSuspension = parent.vmAllowSuspension; this.parent = parent; parent.add(this); }
从注释中可以看到默认Group名为system是由C代码调用。这是一个没有parent的ThreadGroup。
我们通常会给ThreadGroup起一个名称 通过带参构造函数
总结
通过上述一些描述我们可以通过实现比如Android捕获异常直接给开发者服务器上传错误消息
同时也可以提供较为美观的崩溃界面
测试
说到线程池我们可以通过 Executors来完成线程池的构建那么如下几个测试结果如何呢?
static class MyThread implements Runnable { @Override public void run() { System.out.println(10 / 2); System.out.println(10 / 0); System.out.println(10 / 1); } } @Test public void testSubmitExecutor() { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(new MyThread()); executorService.shutdown(); } @Test public void testExecuteExecutor() { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(new MyThread()); executorService.shutdown(); } @Test public void testExceptionSubmitExecutor() { ExecutorService executorService = Executors.newCachedThreadPool(); Thread thread=new Thread(new MyThread()); thread.setUncaughtExceptionHandler(new ExceptionHandler()); executorService.submit(thread); executorService.shutdown(); } @Test public void testExceptionExecuteExecutor() { ExecutorService executorService = Executors.newCachedThreadPool(); Thread thread=new Thread(new MyThread()); thread.setUncaughtExceptionHandler(new ExceptionHandler()); executorService.execute(thread); executorService.shutdown(); } @Test public void testSubmitFutureExecutor() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); Future<?> submit = executorService.submit(new MyThread()); submit.get(); executorService.shutdown(); }
结论可以告诉大家 executorService调用submit或者execute都是Runnable(当然也有submit支持callable)
因此在此封装Thread是无效的,也就是说最终运行的Thread并不是测试代码中new出来的thread。因此所有的exceptionHandler都是无效的配置
那么就涉及到submit和execute的区别
- submit 提交后返回Future 可以判断执行完成与否 而execute不支持
- submit中线程抛出异常可以通过Future的get方法捕获异常 而execute直接就是Threadgroup中unExceptionHandler处理




