您当前的位置: 首页 > 

恐龙弟旺仔

暂无认证

  • 0浏览

    0关注

    282博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

线程之UncaughtExceptionHandler的应用

恐龙弟旺仔 发布时间:2020-03-01 09:58:51 ,浏览量:0

前言:

    看源码好处多多,尤其是优秀的框架源码,受益无穷。

    日常CRUD的工作实在无聊,把CRUD的代码写一万遍也没法提高个人的层级。

    业务的疯狂增长确实能增长个人的技能,因为需要不断思考优化方案,不断去升级(硬件、软件和个人能力)。

    但是要想写出好的代码,好的框架还是需要多借鉴别人的优秀代码。

    笔者个人的感受就是:明明看了很多遍的设计模式,但是平时写代码还都是一个方法搞定所有,无法用到实战中。所以还是要多看看优秀的源码,这样不自觉中就会向这些代码习惯靠拢,也会不自觉的使用设计模式。

 

ZookeeperThread引发的思考:

    今天在看到Zookeeper源码的核心类SendThread和EventThread的时候,对他们的公有父类ZookeeperThread不太理解,源码如下:

/**
 * This is the main class for catching all the uncaught exceptions thrown by the
 * threads.
 */
public class ZooKeeperThread extends Thread {

    private static final Logger LOG = LoggerFactory
            .getLogger(ZooKeeperThread.class);

    private UncaughtExceptionHandler uncaughtExceptionalHandler = new UncaughtExceptionHandler() {

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            handleException(t.getName(), e);
        }
    };

    public ZooKeeperThread(Runnable thread, String threadName) {
        super(thread, threadName);
        setUncaughtExceptionHandler(uncaughtExceptionalHandler);
    }

    public ZooKeeperThread(String threadName) {
        super(threadName);
        setUncaughtExceptionHandler(uncaughtExceptionalHandler);
    }

    protected void handleException(String thName, Throwable e) {
        LOG.warn("Exception occurred from thread {}", thName, e);
    }
}

    代码看着很简单,从类注释上看出来,这是一个可以捕获 该线程抛出的所有未捕获的异常 的类。

    平时笔者从来没写过这种代码,也没有对Thread做过什么特殊处理,所以引发了笔者的好奇。

    UncaughtExceptionHandler到底有什么特殊能力呢?

 

UncaughtExceptionHandler的作用:

    当线程由于未捕获异常即将中止时,JVM将使用thread.getuncaughtexceptionhandler()查询线程的uncaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。如果一个线程没有显式地设置它的UncaughtExceptionHandler,那么它的ThreadGroup对象就充当它的UncaughtExceptionHandler。如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发给默认的未捕获异常处理程序。

    总体来说:当线程由于一个未捕获的异常突然中止时调用的处理程序的接口。

    从这大段文字的描述,我们还是无法直接感受到这个接口的作用,下面来先看个示例

 

    1)常规捕获异常方式
public class NoCaughtThread {
    public static void main(String[] args) {
        try {
            Thread thread = new Thread(new Task());
            thread.start();
        } catch (Exception e) {
            System.out.println("==Exception: " + e.getMessage());
        }
    }
}

class Task implements Runnable {
    @Override
    public void run() {
        System.out.println(3 / 2);
        System.out.println(3 / 0);
        System.out.println(3 / 1);
    }
}
// res
1
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at com.example.demo.Task.run(NoCaughtThread.java:26)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

    从结果值我们来分析下,只输出了一个1,因为到输出第二句3/0的时候报错了,所以抛出了异常ArithmeticException,然后exit出了程序。

    我们发现一个问题就是main线程中的catch并没有生效。

 

    总结:因为在多线程环境下,线程抛出的异常是无法用try...catch捕获的。

    这样可能会导致一些问题:由于这些未捕获的异常导致的线程中止,可能会使我们在线程中处理的系统资源回收、关闭连接这些操作无法处理。

 

    2)使用UncaughtExceptionHandler处理

        我们来将上面的方法改造下

public class WatchCaughtThread {

    public static void main(String[] args) {

        Thread thread = new Thread(new Task());
        thread.setUncaughtExceptionHandler(new LocalUncaughtExceptionHandler());
        thread.start();
    }
}

// 自定义handler
class LocalUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("==Exception: "+e.getMessage());
        // do sth... close connection/release resource and so on.
    }
}
// res
1
==Exception: / by zero

    通过上述结果可以看到,该未知的异常被成功捕获到,我们可以在uncaughtException()方法中做一些释放资源、连接的操作,避免由于线程的突然中止导致资源无法释放。

 

    3)使用线程池处理线程的UncaughtExceptionHandler做法

        我们经常使用jdk线程池来处理线程,那么在线程池中我们该如何自定义UncaughtExceptionHandler异常的处理呢,直接看示例如下:

public class ExecuteCaught {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new ThreadPoolTask());
        exec.shutdown();
    }
}

class ThreadPoolTask implements Runnable {
    @Override
    public void run() {
        // 需要在这里定义UncaughtExceptionHandler
        Thread.currentThread().setUncaughtExceptionHandler(new LocalUncaughtExceptionHandler());
        System.out.println(3 / 2);
        System.out.println(3 / 0);
        System.out.println(3 / 1);
    }
}

    总结:我们对线程池的使用时需要特别注意下,如果要使UncaughtExceptionHandler定义生效,需要在run()方法内部定义。

    

回顾ZookeeperThread:

    我们再来看下ZookeeperThread如何自定义UncaughtExceptionHandler:

// 来自ZookeeperThread.java

private UncaughtExceptionHandler uncaughtExceptionalHandler = new UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            handleException(t.getName(), e);
        }
    };
// 对于这些未知异常,主要是将这些异常明细信息统一打印到Log中,避免异常的缺漏。
    protected void handleException(String thName, Throwable e) {
        LOG.warn("Exception occurred from thread {}", thName, e);
    }

// 使用UncaughtExceptionHandler方式
    public ZooKeeperThread(Runnable thread, String threadName) {
        super(thread, threadName);
        // 直接在构造方法中添加UncaughtExceptionHandler
        setUncaughtExceptionHandler(uncaughtExceptionalHandler);
    }

    总结:通过对ZookeeperThread的学习,我们看到其对未知异常采用的方式是统一捕获,打印明细到Log中,这样就避免了异常缺漏打印。

 

总结:

    我们在日常的工作中,可以考虑使用这种方式,定义一个基础Thread,统一捕获所有异常,做好处理,极力避免未知异常对程序的影响。

    最怕的就是线程异常退出了,但是我们什么日志都抓不到。

 

参考:

    https://blog.csdn.net/weixin_33698823/article/details/90330574  

关注
打赏
1655041699
查看更多评论
立即登录/注册

微信扫码登录

0.9656s