線程之UncaughtExceptionHandler的應用

前言:

    看源碼好處多多,尤其是優秀的框架源碼,受益無窮。

    日常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  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章