面試官:線程池執(zhí)行過程中遇到異常會(huì)發(fā)生什么,怎樣處理?
線程遇到未處理的異常就結(jié)束了
這個(gè)好理解,當(dāng)線程出現(xiàn)未捕獲異常的時(shí)候就執(zhí)行不下去了,留給它的就是垃圾回收了。
線程池中線程頻繁出現(xiàn)未捕獲異常
當(dāng)線程池中線程頻繁出現(xiàn)未捕獲的異常,那線程的復(fù)用率就大大降低了,需要不斷地創(chuàng)建新線程。
public?class?ThreadExecutor?{
?private?ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(1,?1,?60,?TimeUnit.SECONDS,
???new?ArrayBlockingQueue<>(200),?new?ThreadFactoryBuilder().setNameFormat("customThread?%d").build());
?@Test
?public?void?test()?{
??IntStream.rangeClosed(1,?5).forEach(i?->?{
???try?{
????Thread.sleep(100);
???}?catch?(InterruptedException?e)?{
????e.printStackTrace();
???}
???threadPoolExecutor.execute(()?->?{
?????int?j?=?1/0;
??});});
?}
}
新建一個(gè)只有一個(gè)線程的線程池,每隔0.1s提交一個(gè)任務(wù),任務(wù)中是一個(gè)1/0的計(jì)算。
Exception?in?thread?"customThread?0"?java.lang.ArithmeticException:?/?by?zero
?at?thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
?at?java.lang.Thread.run(Thread.java:748)
Exception?in?thread?"customThread?1"?java.lang.ArithmeticException:?/?by?zero
?at?thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
?at?java.lang.Thread.run(Thread.java:748)
Exception?in?thread?"customThread?2"?java.lang.ArithmeticException:?/?by?zero
?at?thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
?at?java.lang.Thread.run(Thread.java:748)
Exception?in?thread?"customThread?3"?java.lang.ArithmeticException:?/?by?zero
?at?thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
?at?java.lang.Thread.run(Thread.java:748)
Exception?in?thread?"customThread?4"?java.lang.ArithmeticException:?/?by?zero
?at?thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
?at?java.lang.Thread.run(Thread.java:748)
Exception?in?thread?"customThread?5"?java.lang.ArithmeticException:?/?by?zero
?at?thread.ThreadExecutor.lambda$null$0(ThreadExecutor.java:25)
?at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
?at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
?at?java.lang.Thread.run(Thread.java:748)
可見每次執(zhí)行的線程都不一樣,之前的線程都沒有復(fù)用。原因是因?yàn)槌霈F(xiàn)了未捕獲的異常。
public?class?ThreadExecutor?{
?private?ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(1,?1,?60,?TimeUnit.SECONDS,
???new?ArrayBlockingQueue<>(200),?new?ThreadFactoryBuilder().setNameFormat("customThread?%d").build());
?@Test
?public?void?test()?{
??IntStream.rangeClosed(1,?5).forEach(i?->?{
???try?{
????Thread.sleep(100);
???}?catch?(InterruptedException?e)?{
????e.printStackTrace();
???}
???threadPoolExecutor.execute(()?->?{
????try?{
?????int?j?=?1?/?0;
????}?catch?(Exception?e)?{
?????System.out.println(Thread.currentThread().getName()?+"?"+?e.getMessage());
????}
???});
??});
?}
}
customThread?0?/?by?zero
customThread?0?/?by?zero
customThread?0?/?by?zero
customThread?0?/?by?zero
customThread?0?/?by?zero
可見當(dāng)異常捕獲了,線程就可以復(fù)用了。另外, 多線程系列面試題和答案全部整理好了,微信搜索Java技術(shù)棧,在后臺(tái)發(fā)送:面試,可以在線閱讀。
問題來了,我們的代碼中異常不可能全部捕獲
如果要捕獲那些沒被業(yè)務(wù)代碼捕獲的異常,可以設(shè)置Thread類的uncaughtExceptionHandler屬性。這時(shí)使用ThreadFactoryBuilder會(huì)比較方便,ThreadFactoryBuilder是guava提供的ThreadFactory生成器。
new?ThreadFactoryBuilder()
.setNameFormat("customThread?%d")
.setUncaughtExceptionHandler((t,?e)?->?System.out.println(t.getName()?+?"發(fā)生異常"?+?e.getCause()))
.build()
修改之后:
public?class?ThreadExecutor?{
?private?static?ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(1,?1,?60,?TimeUnit.SECONDS,
???new?ArrayBlockingQueue<>(200),
???new?ThreadFactoryBuilder()
?????.setNameFormat("customThread?%d")
?????.setUncaughtExceptionHandler((t,?e)?->?System.out.println("UncaughtExceptionHandler捕獲到:"?+?t.getName()?+?"發(fā)生異常"?+?e.getMessage()))
?????.build());
?@Test
?public?void?test()?{
??IntStream.rangeClosed(1,?5).forEach(i?->?{
???try?{
????Thread.sleep(100);
???}?catch?(InterruptedException?e)?{
????e.printStackTrace();
???}
???threadPoolExecutor.execute(()?->?{
????System.out.println("線程"?+?Thread.currentThread().getName()?+?"執(zhí)行");
????int?j?=?1?/?0;
???});
??});
?}
}
線程customThread?0執(zhí)行
UncaughtExceptionHandler捕獲到:customThread 0發(fā)生異常/ by zero
線程customThread?1執(zhí)行
UncaughtExceptionHandler捕獲到:customThread 1發(fā)生異常/ by zero
線程customThread?2執(zhí)行
UncaughtExceptionHandler捕獲到:customThread 2發(fā)生異常/ by zero
線程customThread?3執(zhí)行
UncaughtExceptionHandler捕獲到:customThread 3發(fā)生異常/ by zero
線程customThread?4執(zhí)行
UncaughtExceptionHandler捕獲到:customThread 4發(fā)生異常/ by zero
可見,結(jié)果并不是我們想象的那樣,線程池中原有的線程沒有復(fù)用!所以通過UncaughtExceptionHandler想將異常吞掉使線程復(fù)用這招貌似行不通。它只是做了一層異常的保底處理。
推薦一個(gè) Spring Boot 基礎(chǔ)教程及實(shí)戰(zhàn)示例:https://www.javastack.cn/categories/Spring-Boot/
將excute改成submit試試
public?class?ThreadExecutor?{
?private?static?ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(1,?1,?60,?TimeUnit.SECONDS,
???new?ArrayBlockingQueue<>(200),
???new?ThreadFactoryBuilder()
?????.setNameFormat("customThread?%d")
?????.setUncaughtExceptionHandler((t,?e)?->?System.out.println("UncaughtExceptionHandler捕獲到:"?+?t.getName()?+?"發(fā)生異常"?+?e.getMessage()))
?????.build());
?@Test
?public?void?test()?{
??IntStream.rangeClosed(1,?5).forEach(i?->?{
???try?{
????Thread.sleep(100);
???}?catch?(InterruptedException?e)?{
????e.printStackTrace();
???}
???Future>?future?=?threadPoolExecutor.submit(()?->?{
????System.out.println("線程"?+?Thread.currentThread().getName()?+?"執(zhí)行");
????int?j?=?1?/?0;
???});
???try?{
????future.get();
???}?catch?(InterruptedException?e)?{
????e.printStackTrace();
???}?catch?(ExecutionException?e)?{
????e.printStackTrace();
???}
??});
?}
}
線程customThread?0執(zhí)行
java.util.concurrent.ExecutionException:?java.lang.ArithmeticException:?/?by?zero
線程customThread?0執(zhí)行
java.util.concurrent.ExecutionException:?java.lang.ArithmeticException:?/?by?zero
線程customThread?0執(zhí)行
java.util.concurrent.ExecutionException:?java.lang.ArithmeticException:?/?by?zero
線程customThread?0執(zhí)行
java.util.concurrent.ExecutionException:?java.lang.ArithmeticException:?/?by?zero
線程customThread?0執(zhí)行
java.util.concurrent.ExecutionException:?java.lang.ArithmeticException:?/?by?zero
通過submit提交線程可以屏蔽線程中產(chǎn)生的異常,達(dá)到線程復(fù)用。當(dāng)get()執(zhí)行結(jié)果時(shí)異常才會(huì)拋出。
原因是通過submit提交的線程,當(dāng)發(fā)生異常時(shí),會(huì)將異常保存,待future.get();時(shí)才會(huì)拋出。最新面試題整理好了,點(diǎn)擊Java面試庫小程序在線刷題。
這是Futuretask的部分run()方法,看setException:
public?void?run()?{
????try?{
????????Callable?c?=?callable;
????????if?(c?!=?null?&&?state?==?NEW)?{
????????????V?result;
????????????boolean?ran;
????????????try?{
????????????????result?=?c.call();
????????????????ran?=?true;
????????????}?catch?(Throwable?ex)?{
????????????????result?=?null;
????????????????ran?=?false;
????????????????setException(ex);
????????????}
????????????if?(ran)
????????????????set(result);
????????}
????}?
}
protected?void?setException(Throwable?t)?{
????if?(UNSAFE.compareAndSwapInt(this,?stateOffset,?NEW,?COMPLETING))?{
????????outcome?=?t;
????????UNSAFE.putOrderedInt(this,?stateOffset,?EXCEPTIONAL);?//?final?state
????????finishCompletion();
????}
}
將異常存在outcome對(duì)象中,沒有拋出,再看get方法:
public?V?get()?throws?InterruptedException,?ExecutionException?{
????int?s?=?state;
????if?(s?<=?COMPLETING)
????????s?=?awaitDone(false,?0L);
????return?report(s);
}
private?V?report(int?s)?throws?ExecutionException?{
????Object?x?=?outcome;
????if?(s?==?NORMAL)
????????return?(V)x;
????if?(s?>=?CANCELLED)
????????throw?new?CancellationException();
????throw?new?ExecutionException((Throwable)x);
}
總結(jié)
本文鏈接:https://blog.csdn.net/weixin_37968613/article/details/108407774
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
—?【 THE END 】— 本公眾號(hào)全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲取! 最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) PDF?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
