Java線程池執(zhí)行過(guò)程中遇到異常的原因和解決辦法
線程遇到未處理的異常就結(jié)束了
這個(gè)好理解,當(dāng)線程出現(xiàn)未捕獲異常的時(shí)候就執(zhí)行不下去了,留給它的就是垃圾回收了。
線程池中線程頻繁出現(xiàn)未捕獲異常
當(dāng)線程池中線程頻繁出現(xiàn)未捕獲的異常,那線程的復(fù)用率就大大降低了,需要不斷地創(chuàng)建新線程。
做個(gè)實(shí)驗(yà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(() -> {
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)
可見(jiàn)每次執(zhí)行的線程都不一樣,之前的線程都沒(méi)有復(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
可見(jiàn)當(dāng)異常捕獲了,線程就可以復(fù)用了。
問(wèn)題來(lái)了,代碼中異常不可能全部捕獲
如果要捕獲那些沒(méi)被業(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àn),結(jié)果并不是想象的那樣,線程池中原有的線程沒(méi)有復(fù)用!所以通過(guò)UncaughtExceptionHandler想將異常吞掉使線程復(fù)用這招貌似行不通。它只是做了一層異常的保底處理。
將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
通過(guò)submit提交線程可以屏蔽線程中產(chǎn)生的異常,達(dá)到線程復(fù)用。當(dāng)get()執(zhí)行結(jié)果時(shí)異常才會(huì)拋出。
原因是通過(guò)submit提交的線程,當(dāng)發(fā)生異常時(shí),會(huì)將異常保存,待future.get();時(shí)才會(huì)拋出。
這是Futuretask的部分run()方法,看setException:
public void run() {
try {
Callable<V> 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ì)象中,沒(méi)有拋出,再看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);
}當(dāng)outcome是異常時(shí)才拋出。
總結(jié)
1、線程池中線程中異常盡量手動(dòng)捕獲
2、通過(guò)設(shè)置ThreadFactory的UncaughtExceptionHandler可以對(duì)未捕獲的異常做保底處理,通過(guò)execute提交任務(wù),線程依然會(huì)中斷,而通過(guò)submit提交任務(wù),可以獲取線程執(zhí)行結(jié)果,線程異常會(huì)在get執(zhí)行結(jié)果時(shí)拋出。
以上就是Java線程池執(zhí)行過(guò)程中遇到異常的原因和解決辦法的詳細(xì)內(nèi)容,更多關(guān)于Java線程池執(zhí)行遇到異常的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java?swing實(shí)現(xiàn)應(yīng)用程序?qū)?shù)據(jù)庫(kù)的訪問(wèn)問(wèn)題
這篇文章主要介紹了Java?swing實(shí)現(xiàn)應(yīng)用程序?qū)?shù)據(jù)庫(kù)的訪問(wèn),本次實(shí)驗(yàn)需要做一個(gè)GUI界面和一個(gè)連接查詢功能,在論壇上借鑒了其他大佬獲取網(wǎng)站內(nèi)容的部分代碼,然后自己做了一個(gè)及其簡(jiǎn)陋的swing界面,算是把這個(gè)實(shí)驗(yàn)完成了,需要的朋友可以參考下2022-09-09
Java中的讀寫鎖ReentrantReadWriteLock源碼分析
這篇文章主要介紹了Java中的讀寫鎖ReentrantReadWriteLock源碼分析,ReentrantReadWriteLock 分為讀鎖和寫鎖兩個(gè)實(shí)例,讀鎖是共享鎖,可被多個(gè)線程同時(shí)使用,寫鎖是獨(dú)占鎖,持有寫鎖的線程可以繼續(xù)獲取讀鎖,反之不行,需要的朋友可以參考下2023-12-12
詳解java.lang.NumberFormatException錯(cuò)誤及解決辦法
這篇文章主要介紹了詳解java.lang.NumberFormatException錯(cuò)誤及解決辦法,本文詳解的介紹了錯(cuò)誤的解決方法,感興趣的可以一起來(lái)了解一下2020-05-05
Java面試Logback打印日志如何獲取當(dāng)前方法名稱題解
這篇文章主要為大家介紹了Java面試Logback打印日志如何獲取當(dāng)前方法名稱題解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Java 連接Access數(shù)據(jù)庫(kù)的兩種方式
這篇文章主要介紹了Java 連接Access數(shù)據(jù)庫(kù)的兩種方式,本文著重講解使用JDBC連接操作Access數(shù)據(jù)庫(kù),需要的朋友可以參考下2015-06-06
java簡(jiǎn)單實(shí)現(xiàn)用語(yǔ)音讀txt文檔方法總結(jié)
在本篇文章里小編給大家整理了關(guān)于java簡(jiǎn)單實(shí)現(xiàn)用語(yǔ)音讀txt文檔的詳細(xì)方法總結(jié),有需要的朋友們參考下。2019-06-06
Java中數(shù)據(jù)庫(kù)常用的兩把鎖之樂(lè)觀鎖和悲觀鎖
這篇文章主要介紹了數(shù)據(jù)庫(kù)常用的兩把鎖之樂(lè)觀鎖和悲觀鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
SpringBoot集成Redisson實(shí)現(xiàn)消息隊(duì)列的示例代碼
本文介紹了如何在SpringBoot中通過(guò)集成Redisson來(lái)實(shí)現(xiàn)消息隊(duì)列的功能,包括RedisQueue、RedisQueueInit、RedisQueueListener、RedisQueueService等相關(guān)組件的實(shí)現(xiàn)和測(cè)試,感興趣的可以了解一下2024-10-10

