Java如何主動從當前線程獲取異常信息
Java主動從當前線程獲取異常信息
使用場景
在單個方法內主動捕獲異常,并將異常的錯誤棧信息以日志的方式打出來
寫法
當前方法throw 了 異常,即改方法存在異常的情況,則可以使用如下方式獲取當前線程中的異常:
// 主動獲取當前線程異常棧信息 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); // 以日志的方式打出 ?\n ?每一行錯誤棧信息結束后換行 log.error(StringUtils.join(stackTraceElements, '\n'));
Java捕獲并處理線程異常:Thread及ThreadPoolExecutor線程池異常捕獲
通過Thread.UncaughtExceptionHandler捕獲線程異常
Thread.UncaughtExceptionHandler類的作用:捕獲并處理線程run方法拋出的異常。
使用示例
為單個線程設置異常捕獲
Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> { ? ? System.out.println("報錯線程:" + t.getName()); ? ? System.out.println("線程拋出的異常:" + e); }; Thread thread = new Thread(() -> { ? ? throw new RuntimeException("throw a new Exception ! "); }); thread.setUncaughtExceptionHandler(exceptionHandler); // 設置異常處理器 thread.start();
如果項目中,全局的Thread線程處理異常的方式都相同,那么可以設置一個全局的異常捕獲類。
Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> { ? ? System.out.println("報錯線程:" + t.getName()); ? ? System.out.println("線程拋出的異常:" + e); }; Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
通過這種方式,后續(xù)的Thread都可以公用這個異常處理類。如果需要用其它方式處理異常時,只需要實現1中的內容即可。
部分源碼解析
UncaughtExceptionHandler源碼 // Thread.UncaughtExceptionHandler? @FunctionalInterface public interface UncaughtExceptionHandler { ? ? void uncaughtException(Thread t, Throwable e); }
Thread中的UncaughtExceptionHandler屬性
// 作用于當個Thread線程 private volatile UncaughtExceptionHandler uncaughtExceptionHandler; // static修飾,其可以作用于所有Thread線程 private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
在Thread中,會首先提供Thread私有的異常處理類,然后才是全局
public UncaughtExceptionHandler getUncaughtExceptionHandler() { ? ? return uncaughtExceptionHandler != null ? ? ? ? ? uncaughtExceptionHandler : group; }
實現原理
當線程由于未捕獲的異常而即將終止時,Java虛擬機將使用調用線程的getUncaughtExceptionHandler方法,以此來獲取UncaughtExceptionHandler類,并執(zhí)行其對異常的處理方法。
如果一個線程沒有顯式實現UncaughtExceptionHandler ,那么它的ThreadGroup對象將充當它的UncaughtExceptionHandler類。
// Thread#getUncaughtExceptionHandler public UncaughtExceptionHandler getUncaughtExceptionHandler() { ? ? return uncaughtExceptionHandler != null ? ? ? ? ? uncaughtExceptionHandler : group; }
ThreadGroup實現了UncaughtExceptionHandler類。
public class ThreadGroup implements Thread.UncaughtExceptionHandler { ?? ?// …… ?? ?public void uncaughtException(Thread t, Throwable e) { ?? ??? ?// 父級ThreadGroup的處理方法 ?? ? ? ?if (parent != null) { ?? ? ? ? ? ?parent.uncaughtException(t, e); ?? ? ? ?} else { ?? ? ? ??? ?// Thread 的全局默認處理方法 ?? ? ? ? ? ?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); ?? ? ? ? ? ?} ?? ? ? ?} ?? ?} ?? ?// …… }
ThreadPoolExecutor線程池異常捕獲
使用示例
要捕獲ThreadPoolExecutor線程池中的線程執(zhí)行異常,需要實現被protected修飾的方法afterExecute,在該方法里面處理異常即可。
// ThreadPoolExecutor#afterExecute class ExtendedExecutor extends ThreadPoolExecutor { ? protected void afterExecute(Runnable r, Throwable t) { ? ? super.afterExecute(r, t); ? ? // 如果Runnable是Future類型,那么異常將會直接通過Future返回 ? ? if (t == null && r instanceof Future<?>) { ? ? ? try { ? ? ? ? Object result = ((Future<?>) r).get(); ? ? ? } catch (CancellationException ce) { ? ? ? ? ? t = ce; ? ? ? } catch (ExecutionException ee) { ? ? ? ? ? t = ee.getCause(); ? ? ? } catch (InterruptedException ie) { ? ? ? ? ? Thread.currentThread().interrupt(); // ignore/reset ? ? ? } ? ? } ? ? // 非Future類型 ? ? if (t != null) ? ? ? System.out.println(t); ? } }
注意:實現afterExecute方法時,要正確嵌套多個覆蓋,子類通常應在此方法的開頭調用super.afterExecute,以確保不會破壞其他父類方法的實現。
源碼解析
在ThreadPoolExecutor中,線程任務的實際執(zhí)行方法是runWorker,如下所示:
// ThreadPoolExecutor#runWorker final void runWorker(Worker w) { ? ? Thread wt = Thread.currentThread(); ? ? Runnable task = w.firstTask; // 實際提交的任務 ? ? ? ? ? ? // ………… ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? beforeExecute(wt, task); // task執(zhí)行前的操作 ? ? ? ? ? ? ? ? Throwable thrown = null; // 異常信息保存 ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? task.run(); // 執(zhí)行任務 ? ? ? ? ? ? ? ? } catch (RuntimeException x) { ? ? ? ? ? ? ? ? ? ? thrown = x; throw x; ? ? ? ? ? ? ? ? } catch (Error x) { ? ? ? ? ? ? ? ? ? ? thrown = x; throw x; ? ? ? ? ? ? ? ? } catch (Throwable x) { ? ? ? ? ? ? ? ? ? ? thrown = x; throw new Error(x); ? ? ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? ? ? afterExecute(task, thrown); // task執(zhí)行后的操作,也就包括了異常的捕獲工作 ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? task = null; ? ? ? ? ? ? ? ? w.completedTasks++; ? ? ? ? ? ? ? ? w.unlock(); ? ? ? ? ? ? } ? ? ? ? // …… }
afterExecute方法在ThreadPoolExecutor,并沒有進行任何操作,就是對異常的線程靜默處理
// ThreadPoolExecutor#afterExecute protected void afterExecute(Runnable r, Throwable t) { }
Callable類型的任務,在執(zhí)行時會自己捕獲并維護執(zhí)行中的異常。
// AbstractExecutorService#submit public <T> Future<T> submit(Callable<T> task) { ? ? if (task == null) throw new NullPointerException(); ? ? RunnableFuture<T> ftask = newTaskFor(task); ? ? execute(ftask); ? ? return ftask; } // Callable任務會被封裝成FutureTask protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { ? ? return new FutureTask<T>(callable); }
在FutureTask的run方法中,他們內部會將整個任務用try-catch給包起來。因此,也不會拋出Callable#run執(zhí)行的內部異常。
// FutureTask#run public void run() { ?? ?// …… ? ? try { ? ? ? ? Callable<V> c = callable; ? ? ? ? // …… ? ? ? ? ? ? // 這里將Callable的執(zhí)行過程產生的異常都捕獲了 ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? result = c.call(); ? ? ? ? ? ? ? ? ran = true; ? ? ? ? ? ? } catch (Throwable ex) { ? ? ? ? ? ? ? ? result = null; ? ? ? ? ? ? ? ? ran = false; ? ? ? ? ? ? ? ? setException(ex); ? ? ? ? ? ? } ? ? ? ? ? ? if (ran) ? ? ? ? ? ? ? ? set(result); ? ? ? ? } ? ? } finally { // ……
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Spring中的之啟動過程obtainFreshBeanFactory詳解
這篇文章主要介紹了Spring中的之啟動過程obtainFreshBeanFactory詳解,在refresh時,prepareRefresh后,馬上就調用了obtainFreshBeanFactory創(chuàng)建beanFactory以及掃描bean信息(beanDefinition),并通過BeanDefinitionRegistry注冊到容器中,需要的朋友可以參考下2024-02-02Java中LocalDate日期格式轉換(使用系統(tǒng)時區(qū))
本文主要介紹了Java中LocalDate日期格式轉換(使用系統(tǒng)時區(qū)),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2007-02-02