Java如何主動(dòng)從當(dāng)前線程獲取異常信息
Java主動(dòng)從當(dāng)前線程獲取異常信息
使用場(chǎng)景
在單個(gè)方法內(nèi)主動(dòng)捕獲異常,并將異常的錯(cuò)誤棧信息以日志的方式打出來(lái)
寫(xiě)法
當(dāng)前方法throw 了 異常,即改方法存在異常的情況,則可以使用如下方式獲取當(dāng)前線程中的異常:
// 主動(dòng)獲取當(dāng)前線程異常棧信息 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); // 以日志的方式打出 ?\n ?每一行錯(cuò)誤棧信息結(jié)束后換行 log.error(StringUtils.join(stackTraceElements, '\n'));
Java捕獲并處理線程異常:Thread及ThreadPoolExecutor線程池異常捕獲
通過(guò)Thread.UncaughtExceptionHandler捕獲線程異常
Thread.UncaughtExceptionHandler類(lèi)的作用:捕獲并處理線程run方法拋出的異常。
使用示例
為單個(gè)線程設(shè)置異常捕獲
Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
? ? System.out.println("報(bào)錯(cuò)線程:" + t.getName());
? ? System.out.println("線程拋出的異常:" + e);
};
Thread thread = new Thread(() -> {
? ? throw new RuntimeException("throw a new Exception ! ");
});
thread.setUncaughtExceptionHandler(exceptionHandler); // 設(shè)置異常處理器
thread.start();如果項(xiàng)目中,全局的Thread線程處理異常的方式都相同,那么可以設(shè)置一個(gè)全局的異常捕獲類(lèi)。
Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
? ? System.out.println("報(bào)錯(cuò)線程:" + t.getName());
? ? System.out.println("線程拋出的異常:" + e);
};
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);通過(guò)這種方式,后續(xù)的Thread都可以公用這個(gè)異常處理類(lèi)。如果需要用其它方式處理異常時(shí),只需要實(shí)現(xiàn)1中的內(nèi)容即可。
部分源碼解析
UncaughtExceptionHandler源碼
// Thread.UncaughtExceptionHandler?
@FunctionalInterface
public interface UncaughtExceptionHandler {
? ? void uncaughtException(Thread t, Throwable e);
}Thread中的UncaughtExceptionHandler屬性
// 作用于當(dāng)個(gè)Thread線程 private volatile UncaughtExceptionHandler uncaughtExceptionHandler; // static修飾,其可以作用于所有Thread線程 private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
在Thread中,會(huì)首先提供Thread私有的異常處理類(lèi),然后才是全局
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
? ? return uncaughtExceptionHandler != null ?
? ? ? ? uncaughtExceptionHandler : group;
}實(shí)現(xiàn)原理
當(dāng)線程由于未捕獲的異常而即將終止時(shí),Java虛擬機(jī)將使用調(diào)用線程的getUncaughtExceptionHandler方法,以此來(lái)獲取UncaughtExceptionHandler類(lèi),并執(zhí)行其對(duì)異常的處理方法。
如果一個(gè)線程沒(méi)有顯式實(shí)現(xiàn)UncaughtExceptionHandler ,那么它的ThreadGroup對(duì)象將充當(dāng)它的UncaughtExceptionHandler類(lèi)。
// Thread#getUncaughtExceptionHandler
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
? ? return uncaughtExceptionHandler != null ?
? ? ? ? uncaughtExceptionHandler : group;
}ThreadGroup實(shí)現(xiàn)了UncaughtExceptionHandler類(lèi)。
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
?? ?// ……
?? ?public void uncaughtException(Thread t, Throwable e) {
?? ??? ?// 父級(jí)ThreadGroup的處理方法
?? ? ? ?if (parent != null) {
?? ? ? ? ? ?parent.uncaughtException(t, e);
?? ? ? ?} else {
?? ? ? ??? ?// Thread 的全局默認(rèn)處理方法
?? ? ? ? ? ?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í)行異常,需要實(shí)現(xiàn)被protected修飾的方法afterExecute,在該方法里面處理異常即可。
// ThreadPoolExecutor#afterExecute
class ExtendedExecutor extends ThreadPoolExecutor {
? protected void afterExecute(Runnable r, Throwable t) {
? ? super.afterExecute(r, t);
? ? // 如果Runnable是Future類(lèi)型,那么異常將會(huì)直接通過(guò)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類(lèi)型
? ? if (t != null)
? ? ? System.out.println(t);
? }
}注意:實(shí)現(xiàn)afterExecute方法時(shí),要正確嵌套多個(gè)覆蓋,子類(lèi)通常應(yīng)在此方法的開(kāi)頭調(diào)用super.afterExecute,以確保不會(huì)破壞其他父類(lèi)方法的實(shí)現(xiàn)。
源碼解析
在ThreadPoolExecutor中,線程任務(wù)的實(shí)際執(zhí)行方法是runWorker,如下所示:
// ThreadPoolExecutor#runWorker
final void runWorker(Worker w) {
? ? Thread wt = Thread.currentThread();
? ? Runnable task = w.firstTask; // 實(shí)際提交的任務(wù)
? ? ? ? ? ? // …………
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? beforeExecute(wt, task); // task執(zhí)行前的操作
? ? ? ? ? ? ? ? Throwable thrown = null; // 異常信息保存
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? task.run(); // 執(zhí)行任務(wù)
? ? ? ? ? ? ? ? } 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,并沒(méi)有進(jìn)行任何操作,就是對(duì)異常的線程靜默處理
// ThreadPoolExecutor#afterExecute
protected void afterExecute(Runnable r, Throwable t) { }Callable類(lèi)型的任務(wù),在執(zhí)行時(shí)會(huì)自己捕獲并維護(hù)執(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任務(wù)會(huì)被封裝成FutureTask
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
? ? return new FutureTask<T>(callable);
}在FutureTask的run方法中,他們內(nèi)部會(huì)將整個(gè)任務(wù)用try-catch給包起來(lái)。因此,也不會(huì)拋出Callable#run執(zhí)行的內(nèi)部異常。
// FutureTask#run
public void run() {
?? ?// ……
? ? try {
? ? ? ? Callable<V> c = callable;
? ? ? ? // ……
? ? ? ? ? ? // 這里將Callable的執(zhí)行過(guò)程產(chǎn)生的異常都捕獲了
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? result = c.call();
? ? ? ? ? ? ? ? ran = true;
? ? ? ? ? ? } catch (Throwable ex) {
? ? ? ? ? ? ? ? result = null;
? ? ? ? ? ? ? ? ran = false;
? ? ? ? ? ? ? ? setException(ex);
? ? ? ? ? ? }
? ? ? ? ? ? if (ran)
? ? ? ? ? ? ? ? set(result);
? ? ? ? }
? ? } finally {
// ……以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
maven項(xiàng)目在實(shí)踐中的構(gòu)建管理之路的方法
這篇文章主要介紹了maven項(xiàng)目在實(shí)踐中的構(gòu)建管理之路的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
SpringSecurity中的表單認(rèn)證詳細(xì)解析
這篇文章主要介紹了SpringSecurity中的表單認(rèn)證詳細(xì)解析,在上一篇文章中,我們初步引入了?Spring?Security,并使用其默認(rèn)生效的?HTTP?基本認(rèn)證保護(hù)?URL?資源,在本篇文章中我們使用表單認(rèn)證來(lái)保護(hù)?URL?資源,需要的朋友可以參考下2023-12-12
學(xué)生視角手把手帶你寫(xiě)Java?線程池初版
作者是一個(gè)來(lái)自河源的大三在校生,以下筆記都是作者自學(xué)之路的一些淺薄經(jīng)驗(yàn),如有錯(cuò)誤請(qǐng)指正,將來(lái)會(huì)不斷的完善筆記,幫助更多的Java愛(ài)好者入門(mén)2022-03-03
Spring中的之啟動(dòng)過(guò)程obtainFreshBeanFactory詳解
這篇文章主要介紹了Spring中的之啟動(dòng)過(guò)程obtainFreshBeanFactory詳解,在refresh時(shí),prepareRefresh后,馬上就調(diào)用了obtainFreshBeanFactory創(chuàng)建beanFactory以及掃描bean信息(beanDefinition),并通過(guò)BeanDefinitionRegistry注冊(cè)到容器中,需要的朋友可以參考下2024-02-02
Java開(kāi)發(fā)者必備10大數(shù)據(jù)工具和框架
這篇文章主要為大家詳細(xì)介紹了Java開(kāi)發(fā)者必備10大數(shù)據(jù)工具和框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Java中LocalDate日期格式轉(zhuǎn)換(使用系統(tǒng)時(shí)區(qū))
本文主要介紹了Java中LocalDate日期格式轉(zhuǎn)換(使用系統(tǒng)時(shí)區(qū)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2007-02-02
遞歸出現(xiàn)棧溢出stackoverflow的問(wèn)題及解決
這篇文章主要介紹了關(guān)于遞歸出現(xiàn)棧溢出stackoverflow的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

