4

java.lang.Thread.UncaughtExceptionHandler - JackPeng博客

 3 years ago
source link: http://yuanfentiank789.github.io/2017/05/11/uncaughtexception/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、有什么用

当一个线程抛出异常,如果没有显式处理(即try catch),JVM会将该异常事件报告给该线程对象的Java.lang.Thread.UncaughtExceptionHandler,如果没有设置UncaughtExceptionHandler,那么默认将会把异常栈信息输出到System.err。

所以,无论是线程池也好,自己写的线程也好,如果你希望他在挂了的时候能给到你通知并处理一下,除了显示处理,还可以提供未捕获异常处理器。

二、怎么用

源码解析之后知道原理,再来谈怎么用就多余了,干脆在这一小段直接做铺垫,剩下的都放到源码解析中来谈(本身原理源码也不复杂)。

java.lang.Thread类中和UncaughtExceptionHandler有关系的粗略算下来就8处,未捕获异常的一个实例对象及其set\get方法、一个静态对象及其静态的set\get方法,还有未捕获异常的内部接口定义,最后一个就是Thread内部属性ThreadGroup。

image 是的,你没有看错,第八个就是ThreadGroup,这货是未捕获异常接口的实现类。

image

三、源码解析

实际上在这里上个例子会比较好,但是因为太简单了,就省略了···你要是自己写,就在Thread对象的set方法中传入未捕获异常的匿名对象就好了···

image

从JDK文档中直接可以看到该接口的描述,当一个线程因未捕获的异常而即将终止时,JAVA虚拟机将使用Thread.getUncaughtExceptionHandler()查询该线程以获得其UncaughtExceptionHandler,并调用该handler的uncaughtException()方法,将线程和异常作为参数传递。如果某一线程没有明确设置其UncaughtExceptionHandler,则将他的ThreadGroup对象作为其handler。如果ThreadGroup对象对异常没有什么特殊的要求,那么ThreadGroup可以将调用转发给默认的未捕获异常处理器(即Thread类中定义的静态的未捕获异常处理器对象)。 所以,关于该接口的原理描述基本就那么多了,直接上源码吧。

// 接口定义  
public interface UncaughtExceptionHandler {  
    void uncaughtException(Thread t, Throwable e);  
}  
// 未捕获异常实例属性:未捕获异常  
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;  
// 未捕获异常静态属性:默认未捕获异常  
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;  
// 设置默认的未捕获异常  
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
    SecurityManager sm = System.getSecurityManager();  
    if (sm != null) {  
        sm.checkPermission(  
            new RuntimePermission("setDefaultUncaughtExceptionHandler")  
                );  
    }  
    defaultUncaughtExceptionHandler = eh;  
 }  
// 获取默认未捕获异常  
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){  
    return defaultUncaughtExceptionHandler;  
}  
// 获取未捕获异常  
public UncaughtExceptionHandler getUncaughtExceptionHandler() {  
    // 这里才是高潮,如果没有设置未捕获异常,那么就将group属性当作未捕获异常  
    return uncaughtExceptionHandler != null ?  
        uncaughtExceptionHandler : group;  
}  
// 设置未捕获异常  
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {  
    checkAccess();  
    uncaughtExceptionHandler = eh;  
}  

根据JDK文档的描述,当出现未捕获异常的时候,JVM调用的是Thread.getUncaughtExceptionHandler(),这个方法中,如果你没有调用Thread.setUncaughtExceptionHandler()设置未捕获异常处理器,那么将会返回Thread.group,将ThreadGroup当作未捕获异常处理器,而ThreadGroup实现了UncaughtExceptionHandler,所以转到ThreadGroup的uncaughtException(Thread, Throwable)方法。

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

先忽略其他不重要的信息(什么parent,这个后面再讲,涉及到该类的构造函数和Thread类的init方法,也不复杂),这里直接调用Thread类中的静态方法获取静态属性默认未捕获异常处理器。

所以源码追下来看,和JDK文档中保持一致。剩下的几个问题,一是Thread类中ThreadGroup的初始化,二是ThreadGroup中parent的追踪(这里就省略了,两个构造函数,瞅一眼就好了···)。

image

关于Thread中ThreadGroup的设置,其实在Thread的所有构造函数中都会转调init方法,其逻辑就是如果在实例化线程对象的时候没有默认传入ThreadGroup,那么就会通过Thread.currentThread.getThreadGroup来得到线程组对象,main方法中有一个默认的main线程组,所以,即便你不传入,还是会有一个默认的。可以写个小demo打印看看,这里就省略了。


    private void init(ThreadGroup g, Runnable target, String name,  
                  long stackSize, AccessControlContext acc) {  
      if (name == null) {  
        throw new NullPointerException("name cannot be null");  
      }  
  
    this.name = name.toCharArray();  
  
    Thread parent = currentThread();  
    SecurityManager security = System.getSecurityManager();  
    if (g == null) {  
        if (security != null) {  
            g = security.getThreadGroup();  
        }  
        if (g == null) {  
            g = parent.getThreadGroup();  
        }  
    }  
    g.checkAccess();  
  
    if (security != null) {  
        if (isCCLOverridden(getClass())) {  
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);  
        }  
    }  
  
    g.addUnstarted();  
  
    this.group = g;  
    this.daemon = parent.isDaemon();  
    this.priority = parent.getPriority();  
    if (security == null || isCCLOverridden(parent.getClass()))  
        this.contextClassLoader = parent.getContextClassLoader();  
    else  
        this.contextClassLoader = parent.contextClassLoader;  
    this.inheritedAccessControlContext =  
            acc != null ? acc : AccessController.getContext();  
    this.target = target;  
    setPriority(priority);  
    if (parent.inheritableThreadLocals != null)  
        this.inheritableThreadLocals =  
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);  
    /* Stash the specified stack size in case the VM cares */  
    this.stackSize = stackSize;  
  
    /* Set thread ID */  
    tid = nextThreadID();  
    }



漏了一个分发的方法,即JVM调用该方法从而得到未捕获异常处理器,这里补上···

image

uncaughtExceptionHandler

如果设置了实例属性uncaughtExceptionHandler(每个线程对象独有),则调用该处理器对未捕获异常进行处理;

// Thread
   private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    
     public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

ThreadGroup ==> defaultUncaughtExceptionHandler

如果没有设置未捕获异常处理器(即1中的uncaughtExceptionHandler),则将线程对象的ThreadGroup当作未捕获异常处理器,在ThreadGroup中获取所以线程对象共享的静态属性defaultUncaughtExceptionHandler来处理未捕获异常(前提是静态的set方法你调用了并且传入处理器实例);

```
// ThreadGroup
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);
        }
    }
} ```

System.err

如果两个set方法都没有调用,那么异常栈信息将被推送到System.err进行处理;



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK