使用UncaughtExceptionHandler重啟線程


先復習Java中的異常

java.lang.Throwable  頂層父類

  |– Error錯誤:JVM內部的嚴重問題,如OOM,程序員無法在代碼中無法處理。

  |–Exception異常:普通的問題。通過合理的處理,程序還可以回到正常執行流程。要求程序員要進行處理。

    |–RuntimeException:未檢查異常(unchecked exception)。  這類異常是程序員的邏輯問題,由於程序員的疏忽導致的錯誤(如數組越界,空指針等)。

      Java編譯器不進行強制要求處理。 也就是說,這類異常在程序中,可以進行處理,也可以不處理。 

    |–非RuntimeException:已檢查異常(checked exception)、編譯時異常。這類異常是由一些外部的偶然因素所引起的。Java編譯器強制要求處理。也就是說,

      程序必須進行對這類異常進行處理,throw,throws或者try catch。

 

而線程Thread的的run()方法不接受拋出語句,因此對於已檢查異常,我們必須進行捕獲並處理,如:

    @Override
    public void run() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File(""));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

 

而對於未檢查異常,如果在run()方法中運行出現了未檢查異常,那么默認的行為是將堆棧跟蹤信息寫到控制台中(或者記錄到錯誤日志文件中),然后退出程序。

Java為我們提供了一個機制,用來捕獲並處理在一個線程對象中拋出的未檢查異常,以避免程序終止。用UncaughtExceptionHandler來實現這種機制。

 

不使用UncaughtExceptionHandler的場景:

public class MyThread implements Runnable {

    @Override
    public void run() {
        Integer.parseInt("yangyongjie");
        System.out.println("expect");
    }

}

控制台輸出:

Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "yangyongjie"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at com.yang.spbo.other.thread.MyThread.run(MyThread.java:15)
    at java.lang.Thread.run(Thread.java:745)

此時,線程立即終止,並沒有執行之后的代碼輸出字符串“expect”。若在執行很重要的代碼之前,出現了未檢查異常,導致了線程終止,那么就會導致業務異常。

 

使用UncaughtExceptionHandler后:

  首先,自定義異常處理器實現UncaughtExceptionHandler接口,用來捕獲和處理運行時出現的未檢查異常(RuntimeException)

public class CustomUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomUncaughtExceptionHandler.class);

    /**
     * 可以自定義處理方案,如不斷重試、將異常任務存入數據庫等
     *
     * @param t
     * @param e
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        LOGGER.error("An exception has been captured,Thread: {}", t.getId());
        LOGGER.error("Exception: {}: {}", e.getClass().getName(), e.getMessage());
        LOGGER.error("Thread status: {}", t.getState());
        new Thread(new MyThread()).start();
    }
}

 

接着,將異常處理器添加到線程中

public class MyThread implements Runnable {

    @Override
    public void run() {
        // 異常處理器
        Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        Integer.parseInt("yangyongjie");
        System.out.println("expect");
    }
}

再次運行,程序能夠持續執行run方法。實際上,如果線程完成了任務,那么它在退出時不會拋出任何異常,從而完成自身生命周期

An exception has been captured
Thread: 10
Exception: java.lang.NumberFormatException: For input string: "yangyongjie"
Stack Trace: 
java.lang.NumberFormatException: For input string: "yangyongjie"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at com.yang.spbo.other.thread.MyThread.run(MyThread.java:22)
    at java.lang.Thread.run(Thread.java:745)
Thread status: RUNNABLE
An exception has been captured
Thread: 12
Exception: java.lang.NumberFormatException: For input string: "yangyongjie"
Stack Trace: 
java.lang.NumberFormatException: For input string: "yangyongjie"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at com.yang.spbo.other.thread.MyThread.run(MyThread.java:22)
    at java.lang.Thread.run(Thread.java:745)
Thread status: RUNNABLE

請注意:UncaughtExceptionHandler可以在無需重啟線程的條件下,將日志記錄變得更加健壯,因為默認日志在線程執行失敗時,不會提供足夠的上下文信息。

 

線程池異常處理,自定義創建線程的工廠,在創建線程時指定

/**
 * 創建線程的工廠,指定有意義的線程組名稱,方便回溯
 *
 * @author yangyongjie
 * @date 2019/8/14
 * @desc
 */
public class CustomThreadFactory implements ThreadFactory {
    /**
     * 線程池中的線程名稱前綴
     */
    private String namePrefix;

    /**
     * 用於線程的名稱遞增排序
     */
    private AtomicInteger atomicInteger = new AtomicInteger(1);

    public CustomThreadFactory(String whatFeaturOfGroup) {
        this.namePrefix = "From CustomThreadFactory-" + whatFeaturOfGroup + "-worker-";
    }

    @Override
    public Thread newThread(Runnable r) {
        String name = namePrefix + atomicInteger.getAndIncrement();
        Thread thread = new Thread(r, name);
        thread.setUncaughtExceptionHandler(new CustomUncaughtExceptionHandler());
        return thread;
    }
}

/**
 * 用來捕獲和處理運行時出現的未檢查異常(RuntimeException)
 * 並重啟線程
 *
 * @author yangyongjie
 * @date 2019/11/26
 * @desc
 */
public class CustomUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomUncaughtExceptionHandler.class);

    /**
     * 可以自定義處理方案,如不斷重試、將異常任務存入數據庫等
     *
     * @param t
     * @param e
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        LOGGER.error("uncaughtException:" + e.getMessage(), e);
        MDCUtil.removeWithOutContext();
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM