Java多线程:捕获线程异常
你處理過多線程中的異常嗎?如何捕獲多線程中發(fā)生的異常?捕獲子線程的異常與捕獲當(dāng)前線程的異常一樣簡單嗎?
除了try catch。Java中還可以通過異常處理器UncaughtExceptionHandler來處理那些未捕獲的異常。
# 在當(dāng)前線程捕獲當(dāng)前線程發(fā)生的異常:
/***?@author?futao*?@date?2020/6/17*/ @Slf4j public?class?ExceptionInCurThread?{public?static?void?main(String[]?args)?{try?{throw?new?RuntimeException("在主線程拋出異常,在主線程捕獲");}?catch?(RuntimeException?e)?{log.error("捕獲到異常",?e);}} }-
結(jié)果:
-
結(jié)論:在當(dāng)前線程通過try catch可以捕獲當(dāng)前線程拋出的異常。
# 可以在當(dāng)前通過try catch的方式捕獲其他線程拋出的異常嗎?'
/***?@author?喜歡天文的pony站長*?Created?on?2020/6/16.*/ public?class?ExceptionInChildThread?implements?Runnable?{@Overridepublic?void?run()?{throw?new?RuntimeException("子線程發(fā)生了異常...");}/***?模擬子線程發(fā)生異常**?@throws?InterruptedException*/private?static?void?exceptionThread()?throws?InterruptedException?{new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}/***?在主線程嘗試通過try?catch捕獲異常*/private?static?void?catchInMain()?{try?{exceptionThread();}?catch?(Exception?e)?{//無法捕獲發(fā)生在其他線程中的異常log.error("捕獲到了異常?",?e);}}public?static?void?main(String[]?args)?throws?InterruptedException?{ExceptionInChildThread.catchInMain();}}-
(錯誤的)預(yù)期:
-
在運(yùn)行第一個(gè)線程的時(shí)候發(fā)生了異常,被catch捕獲,打印捕獲到了異常?和異常堆棧且后面的線程將不會運(yùn)行。
-
-
實(shí)際運(yùn)行結(jié)果:
-
并不符合預(yù)期。
-
沒有被try catch捕獲。
-
后續(xù)的線程沒有因?yàn)榈谝粋€(gè)線程發(fā)生異常而跳過。
-
-
結(jié)論:
-
無法在一個(gè)線程中通過try catch捕獲另外一個(gè)線程的異常。
-
# 解決方案
在每個(gè)線程內(nèi)部run()方法內(nèi)通過try catch捕獲當(dāng)前線程發(fā)生的異常。
-
缺點(diǎn):每個(gè)線程都需要編寫重復(fù)的try catch 代碼
使用線程異常處理器UncaughtExceptionHandler
-
給所有線程設(shè)置統(tǒng)一的異常處理器
-
給每個(gè)線程設(shè)置特定的異常處理器
-
給線程組設(shè)置異常處理器
-
給線程池設(shè)置異常處理器
-
因?yàn)榫€程池也是通過new Thread()的方式創(chuàng)建的線程,所以思想與上面兩種方法一致。
-
注意:execute()與submit()方式對????異常處理的不同。
-
# 在線程內(nèi)部run()通過try catch捕獲異常
/***?@author?喜歡天文的pony站長*?Created?on?2020/6/16.*/ @Slf4j public?class?ExceptionInChildThread?implements?Runnable?{@Overridepublic?void?run()?{try?{//do?something?else...throw?new?RuntimeException("子線程發(fā)生了異常...");}?catch?(Exception?e)?{log.error("在線程內(nèi)部捕獲異常",?e);}}/***?模擬子線程發(fā)生異常**?@throws?InterruptedException*/private?static?void?exceptionThread()?throws?InterruptedException?{new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}/***?在主線程嘗試通過try?catch捕獲異常*/private?static?void?catchInMain()?{try?{exceptionThread();}?catch?(Exception?e)?{//無法捕獲發(fā)生在其他線程中的異常log.error("捕獲到了異常?",?e);}}public?static?void?main(String[]?args)?throws?InterruptedException?{ExceptionInChildThread.catchInMain();} }-
結(jié)果:
-
成功在子線程內(nèi)部run()方法捕獲到了異常
-
# 使用線程異常處理器UncaughtExceptionHandler
當(dāng)一個(gè)線程由于未捕獲異常而退出時(shí),JVM會把這個(gè)事件報(bào)告給應(yīng)用程序提供的UncaughtExceptionHandler異常處理器
-
自定義線程異常處理器
-
使用:
1. 全局:
-
Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
-
通過調(diào)用Thread的靜態(tài)方法setDefaultUncaughtExceptionHandler(),設(shè)置Thread的靜態(tài)屬性defaultUncaughtExceptionHandler.為我們自定義的異常處理器。
-
源碼:
-
測試:
/***?@author?喜歡天文的pony站長*?Created?on?2020/6/16.*/ @Slf4j public?class?ExceptionInChildThread?implements?Runnable?{@Overridepublic?void?run()?{throw?new?RuntimeException("子線程發(fā)生了異常...");}/***?模擬子線程發(fā)生異常**?@throws?InterruptedException*/private?static?void?exceptionThread()?throws?InterruptedException?{new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);new?Thread(new?ExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}public?static?void?main(String[]?args)?throws?InterruptedException?{//設(shè)置全局的線程異常處理器Thread.setDefaultUncaughtExceptionHandler(new?CustomThreadUncaughtExceptionHandler());exceptionThread();} }-
結(jié)果: 成功捕獲
2. 為指定線程設(shè)置特定的異常處理器
-
細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了,在上面Thread類的截圖中,還有一個(gè)實(shí)例屬性private volatile UncaughtExceptionHandler uncaughtExceptionHandler;。通過給這個(gè)屬性賦值,可以實(shí)現(xiàn)為每個(gè)線程對象設(shè)置不同的異常處理器。
-
測試使用
-
結(jié)果: 成功捕獲線程1的異常信息
3. 線程組
/***?@author?futao*?@date?2020/6/20*/ @Slf4j public?class?ExceptionInThreadGroup?implements?Runnable?{@Overridepublic?void?run()?{throw?new?RuntimeException("線程任務(wù)發(fā)生了異常");}public?static?void?main(String[]?args)?throws?InterruptedException?{ThreadGroup?threadGroup?=?new?ThreadGroup("只知道拋出異常的線程組...")?{@Overridepublic?void?uncaughtException(Thread?t,?Throwable?e)?{super.uncaughtException(t,?e);log.error("線程組內(nèi)捕獲到線程[{},{}]異常",?t.getId(),?t.getName(),?e);}};ExceptionInThreadGroup?exceptionInThreadGroup?=?new?ExceptionInThreadGroup();new?Thread(threadGroup,?exceptionInThreadGroup,?"線程1").start();TimeUnit.MILLISECONDS.sleep(300L);//優(yōu)先獲取綁定在thread對象上的異常處理器Thread?thread?=?new?Thread(threadGroup,?exceptionInThreadGroup,?"線程2");thread.setUncaughtExceptionHandler(new?CustomThreadUncaughtExceptionHandler());thread.start();TimeUnit.MILLISECONDS.sleep(300L);new?Thread(threadGroup,?exceptionInThreadGroup,?"線程3").start();} }-
結(jié)果:
4. 線程池
/***?@author?futao*?@date?2020/6/17*/ public?class?CatchThreadPoolException?{public?static?void?main(String[]?args)?{ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(2,4,1L,TimeUnit.MINUTES,new?LinkedBlockingDeque<>(1024),new?ThreadFactory()?{@Overridepublic?Thread?newThread(Runnable?r)?{Thread?thread?=?new?Thread(r);//設(shè)置線程異常處理器thread.setUncaughtExceptionHandler(new?CustomThreadUncaughtExceptionHandler());return?thread;}});threadPoolExecutor.execute(new?Runnable()?{@Overridepublic?void?run()?{throw?new?RuntimeException("execute()發(fā)生異常");}});threadPoolExecutor.submit(new?Runnable()?{@Overridepublic?void?run()?{throw?new?RuntimeException("submit.run()發(fā)生異常");}});threadPoolExecutor.submit(new?Callable<String>()?{@Overridepublic?String?call()?throws?Exception?{throw?new?RuntimeException("submit.call()發(fā)生異常");}});threadPoolExecutor.shutdown();} }-
結(jié)果: 并不符合預(yù)期,預(yù)期應(yīng)該捕獲三個(gè)異常
-
只捕獲到了通過execute()提交的任務(wù)的異常
-
沒有捕獲到通過submit()提交的任務(wù)的異常
-
-
通過afterExecute()捕獲submit()任務(wù)的異常
-
通過submit()方法的源碼可以發(fā)現(xiàn),submit()是將runnable()封裝成了RunnableFuture<Void>,并最終調(diào)用execute(ftask);執(zhí)行。
-
歡迎在評論區(qū)留下你看文章時(shí)的思考,及時(shí)說出,有助于加深記憶和理解,還能和像你一樣也喜歡這個(gè)話題的讀者相遇~
# 本文源代碼
-
https://github.com/FutaoSmile/learn-thread/tree/master/src/main/java/com/futao/learn/threads/捕獲線程異常
總結(jié)
以上是生活随笔為你收集整理的Java多线程:捕获线程异常的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打破你的认知,数字除以 0 一定会崩溃吗
- 下一篇: Java多线程:线程属性