Java中捕獲線程異常的幾種方式總結(jié)
首先,我們要知道,在Java中,線程中的異常是不能拋出到調(diào)用該線程的外部方法中捕獲的。
為什么不能拋出到外部線程捕獲?
因?yàn)榫€程是獨(dú)立執(zhí)行的代碼片斷,線程的問題應(yīng)該由線程自己來解決,而不要委托到外部。
基于這樣的設(shè)計(jì)理念,在Java中,線程方法的異常都應(yīng)該在線程代碼邊界之內(nèi)(run方法內(nèi))進(jìn)行try catch并處理掉。
換句話說,我們不能捕獲從線程中逃逸的異常。
怎么進(jìn)行的限制?
通過java.lang.Runnable.run()方法聲明(因?yàn)榇朔椒暶魃蠜]有throw exception部分)進(jìn)行了約束。
如果在線程中拋出了線程會(huì)怎么樣?
線程會(huì)立即終結(jié)。
現(xiàn)在我們可以怎樣捕獲線程中的異常?
Java中在處理異常的時(shí)候,通常的做法是使用try-catch-finally來包含代碼塊,但是Java自身還有一種方式可以處理——使用UncaughtExceptionHandler。
它能檢測出某個(gè)線程由于未捕獲的異常而終結(jié)的情況。
當(dāng)一個(gè)線程由于未捕獲異常而退出時(shí),JVM會(huì)把這個(gè)事件報(bào)告給應(yīng)用程序提供的UncaughtExceptionHandler異常處理器(這是Thread類中的接口):
//Thread類中的接口 public interface UncaughtExceptionHanlder { ?? ?void uncaughtException(Thread t, Throwable e); }
JDK5之后允許我們在每一個(gè)Thread對象上添加一個(gè)異常處理器UncaughtExceptionHandler 。
Thread.UncaughtExceptionHandler.uncaughtException()方法會(huì)在線程因未捕獲的異常而面臨死亡時(shí)被調(diào)用。
首先要先定義一個(gè)異常捕獲器:
public class MyUnchecckedExceptionhandler implements UncaughtExceptionHandler { ? ? @Override ? ? public void uncaughtException(Thread t, Throwable e) { ? ? ? ? System.out.println("捕獲異常處理方法:" + e); ? ? } }
方法1. 創(chuàng)建線程時(shí)設(shè)置異常處理Handler
Thread t = new Thread(new ExceptionThread()); t.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler()); t.start();
方法2. 使用Executors創(chuàng)建線程時(shí),還可以在ThreadFactory中設(shè)置
ExecutorService exec = Executors.newCachedThreadPool(new ThreadFactory(){ ? ? ? ? ? ? @Override ? ? ? ? ? ? public Thread newThread(Runnable r) { ? ? ? ? ? ? ? ? Thread thread = new Thread(r); ? ? ? ? ? ? ? ? thread.setUncaughtExceptionHandler(new MyUnchecckedExceptionhandler()); ? ? ? ? ? ? ? ? return thread; ? ? ? ? ? ? } }); exec.execute(new ExceptionThread());
不過,上面的結(jié)果能證明:通過execute方式提交的任務(wù),能將它拋出的異常交給異常處理器。
如果改成submit方式提交任務(wù),則異常不能被異常處理器捕獲,這是為什么呢?
查看源碼后可以發(fā)現(xiàn),如果一個(gè)由submit提交的任務(wù)由于拋出了異常而結(jié)束,那么這個(gè)異常將被Future.get封裝在ExecutionException中重新拋出。
所以,通過submit提交到線程池的任務(wù),無論是拋出的未檢查異常還是已檢查異常,都將被認(rèn)為是任務(wù)返回狀態(tài)的一部分,因此不會(huì)交由異常處理器來處理。
java.util.concurrent.FutureTask 源碼
public V get() throws InterruptedException, ExecutionException { ? ? int s = state; ? ? if (s <= COMPLETING)//如果任務(wù)沒有結(jié)束,則等待結(jié)束 ? ? ? ? s = awaitDone(false, 0L); ? ? return report(s);//如果執(zhí)行結(jié)束,則報(bào)告執(zhí)行結(jié)果 } @SuppressWarnings("unchecked") private V report(int s) throws ExecutionException { ? ? Object x = outcome; ? ? if (s == NORMAL)//如果執(zhí)行正常,則返回結(jié)果 ? ? ? ? return (V)x; ? ? if (s >= CANCELLED)//如果任務(wù)被取消,調(diào)用get則報(bào)CancellationException ? ? ? ? throw new CancellationException(); ? ? throw new ExecutionException((Throwable)x);//執(zhí)行異常,則拋出ExecutionException }
方法3. 使用線程組ThreadGroup
//1.創(chuàng)建線程組 ThreadGroup threadGroup = ? ? ? ? // 這是匿名類寫法 ? ? ? ? new ThreadGroup("group") { ? ? ? ? ? ? // 繼承ThreadGroup并重新定義以下方法 ? ? ? ? ? ? // 在線程成員拋出unchecked exception 會(huì)執(zhí)行此方法 ? ? ? ? ? ? @Override ? ? ? ? ? ? public void uncaughtException(Thread t, Throwable e) { ? ? ? ? ? ? ? ? //4.處理捕獲的線程異常 ? ? ? ? ? ? } ? ? ? ? }; //2.創(chuàng)建Thread ? ? ? ? Thread thread = new Thread(threadGroup, new Runnable() { ? ? @Override ? ? public void run() { ? ? ? ? System.out.println(1 / 0); ? ? } }, "my_thread"); ? //3.啟動(dòng)線程 thread.start(); ??
方法4. 默認(rèn)的線程異常捕獲器
如果我們只需要一個(gè)線程異常處理器處理線程的異常,那么我們可以設(shè)置一個(gè)默認(rèn)的線程異常處理器,當(dāng)線程出現(xiàn)異常時(shí),
如果我們沒有指定線程的異常處理器,而且線程組也沒有設(shè)置,那么就會(huì)使用默認(rèn)的線程異常處理器
// 設(shè)置默認(rèn)的線程異常捕獲處理器 Thread.setDefaultUncaughtExceptionHandler(new MyUnchecckedExceptionhandler());
上面說的4種方法都是基于線程異常處理器實(shí)現(xiàn)的,接下來將的幾種方法則不需要依賴異常處理器。
方法5. 使用FetureTask來捕獲異常
//1.創(chuàng)建FeatureTask FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() { ? ? @Override ? ? public Integer call() throws Exception { ? ? ? ? return 1/0; ? ? } }); //2.創(chuàng)建Thread Thread thread = new Thread(futureTask); //3.啟動(dòng)線程 thread.start(); try { ? ? Integer result = futureTask.get(); } catch (InterruptedException e) { ? ? e.printStackTrace(); } catch (ExecutionException e) { ? ? //4.處理捕獲的線程異常 }
方法6. 利用線程池提交線程時(shí)返回的Feature引用
//1.創(chuàng)建線程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //2.創(chuàng)建Callable,有返回值的,你也可以創(chuàng)建一個(gè)線程實(shí)現(xiàn)Callable接口。 // ?如果你不需要返回值,這里也可以創(chuàng)建一個(gè)Thread即可,在第3步時(shí)submit這個(gè)thread。 Callable<Integer> callable = new Callable<Integer>() { ? ? @Override ? ? public Integer call() throws Exception { ? ? ? ? return 1/0; ? ? } }; //3.提交待執(zhí)行的線程 Future<Integer> future = executorService.submit(callable); try { ? ? ?Integer result = future.get(); } catch (InterruptedException e) { ? ? e.printStackTrace(); } catch (ExecutionException e) { ? ? //4.處理捕獲的線程異常 }
實(shí)現(xiàn)原理可以看一下方法2的說明。
方法6本質(zhì)上和方法5一樣是基于FutureTask實(shí)現(xiàn)的。
方法7. 重寫ThreadPoolExecutor的afterExecute方法
//1.創(chuàng)建線程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, ? ? ? ? new LinkedBlockingQueue<>()) { ? ? @Override ? ? protected void afterExecute(Runnable r, Throwable t) { ? ? ? ? if (r instanceof Thread) { ? ? ? ? ? ? if (t != null) { ? ? ? ? ? ? ? ? //處理捕獲的異常 ? ? ? ? ? ? } ? ? ? ? } else if (r instanceof FutureTask) { ? ? ? ? ? ? FutureTask futureTask = (FutureTask) r; ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? futureTask.get(); ? ? ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? ? ? } catch (ExecutionException e) { ? ? ? ? ? ? ? ? //處理捕獲的異常 ? ? ? ? ? ? } ? ? ? ? } ? ? } }; Thread t1 = new Thread(() -> { ? ? int c = 1 / 0; }); threadPoolExecutor.execute(t1); Callable<Integer> callable = () -> 2 / 0; threadPoolExecutor.submit(callable);
總結(jié)
線程最好交由線程池進(jìn)行管理。
線程池中如果你的線程不需要返回值則可以使用方法2,利用ThreadFactory為線程指定統(tǒng)一的異常處理器。記得一定要用execute方式提交任務(wù),否則異常處理器捕獲不到異常。
線程池中如果你的線程需要返回值,你又想捕獲線程異常,則需要借助FutureTask,即使用方法6。使用submit方法提交線程。當(dāng)然了,不需要返回值的情況也可以使用方法6。
方法7同時(shí)支持execute和submit提交任務(wù)時(shí)異常的捕獲,適合相同類型任務(wù)的統(tǒng)一異常處理,但是異常處理粒度較粗,而方法2和方法6可以針對每個(gè)任務(wù)進(jìn)行自定義的異常處理。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中Date與String相互轉(zhuǎn)換的方法
這篇文章主要為大家詳細(xì)介紹了Java中Date與String相互轉(zhuǎn)換方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10基于接口實(shí)現(xiàn)java動(dòng)態(tài)代理示例
這篇文章主要介紹了基于接口實(shí)現(xiàn)java動(dòng)態(tài)代理示例,需要的朋友可以參考下2014-04-04MyBatis分頁查詢返回list的時(shí)候出現(xiàn)null的問題
這篇文章主要介紹了MyBatis分頁查詢返回list的時(shí)候出現(xiàn)null的問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07Spring?Boot中KafkaListener的介紹、原理和使用方法案例詳解
本文介紹了Spring Boot中 @KafkaListener 注解的介紹、原理和使用方法,通過本文的介紹,我們希望讀者能夠更好地理解Spring Boot中 @KafkaListener 注解的使用方法,并在項(xiàng)目中更加靈活地應(yīng)用2023-09-09springboot中使用jpa下hibernate的ddl-auto方式
這篇文章主要介紹了springboot中使用jpa下hibernate的ddl-auto方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02淺談兩個(gè)jar包中包含完全相同的包名和類名的加載問題
下面小編就為大家?guī)硪黄獪\談兩個(gè)jar包中包含完全相同的包名和類名的加載問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09