Java線程池執(zhí)行過程中遇到異常的原因和解決辦法
線程遇到未處理的異常就結束了
這個好理解,當線程出現(xiàn)未捕獲異常的時候就執(zhí)行不下去了,留給它的就是垃圾回收了。
線程池中線程頻繁出現(xiàn)未捕獲異常
當線程池中線程頻繁出現(xiàn)未捕獲的異常,那線程的復用率就大大降低了,需要不斷地創(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;
});});
}
}新建一個只有一個線程的線程池,每隔0.1s提交一個任務,任務中是一個1/0的計算。
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í)行的線程都不一樣,之前的線程都沒有復用。原因是因為出現(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
可見當異常捕獲了,線程就可以復用了。
問題來了,代碼中異常不可能全部捕獲
如果要捕獲那些沒被業(yè)務代碼捕獲的異常,可以設置Thread類的uncaughtExceptionHandler屬性。這時使用ThreadFactoryBuilder會比較方便,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
可見,結果并不是想象的那樣,線程池中原有的線程沒有復用!所以通過UncaughtExceptionHandler想將異常吞掉使線程復用這招貌似行不通。它只是做了一層異常的保底處理。
將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)生的異常,達到線程復用。當get()執(zhí)行結果時異常才會拋出。
原因是通過submit提交的線程,當發(fā)生異常時,會將異常保存,待future.get();時才會拋出。
這是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對象中,沒有拋出,再看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);
}當outcome是異常時才拋出。
總結
1、線程池中線程中異常盡量手動捕獲
2、通過設置ThreadFactory的UncaughtExceptionHandler可以對未捕獲的異常做保底處理,通過execute提交任務,線程依然會中斷,而通過submit提交任務,可以獲取線程執(zhí)行結果,線程異常會在get執(zhí)行結果時拋出。
以上就是Java線程池執(zhí)行過程中遇到異常的原因和解決辦法的詳細內(nèi)容,更多關于Java線程池執(zhí)行遇到異常的資料請關注腳本之家其它相關文章!
相關文章
idea兩側的maven-project-structure圖標不見了如何解決
這篇文章主要介紹了如何解決idea兩側的maven-project-structure圖標不見了問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
SpringCloud通過Nacos實現(xiàn)注冊中心與遠程服務調(diào)用詳解流程
如果不滿足eureka注冊中心,那么本文記錄的Nacos是不二之選。本文主要記錄Springboot基于Nacos實現(xiàn)注冊中心以及遠程服務調(diào)用2022-07-07

