先復習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(); } }