面试官:线程池执行过程中遇到异常会发生什么,怎样处理?
共 8070字,需浏览 17分钟
·
2021-10-21 12:50
线程遇到未处理的异常就结束了
这个好理解,当线程出现未捕获异常的时候就执行不下去了,留给它的就是垃圾回收了。
线程池中线程频繁出现未捕获异常
当线程池中线程频繁出现未捕获的异常,那线程的复用率就大大降低了,需要不断地创建新线程。
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)
可见每次执行的线程都不一样,之前的线程都没有复用。原因是因为出现了未捕获的异常。
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
可见当异常捕获了,线程就可以复用了。另外, 多线程系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。
问题来了,我们的代码中异常不可能全部捕获
如果要捕获那些没被业务代码捕获的异常,可以设置Thread类的uncaughtExceptionHandler
属性。这时使用ThreadFactoryBuilder
会比较方便,ThreadFactoryBuilder
是guava提供的ThreadFactory生成器。
new ThreadFactoryBuilder()
.setNameFormat("customThread %d")
.setUncaughtExceptionHandler((t, e) -> System.out.println(t.getName() + "发生异常" + 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() + "发生异常" + 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() + "执行");
int j = 1 / 0;
});
});
}
}
线程customThread 0执行
UncaughtExceptionHandler捕获到:customThread 0发生异常/ by zero
线程customThread 1执行
UncaughtExceptionHandler捕获到:customThread 1发生异常/ by zero
线程customThread 2执行
UncaughtExceptionHandler捕获到:customThread 2发生异常/ by zero
线程customThread 3执行
UncaughtExceptionHandler捕获到:customThread 3发生异常/ by zero
线程customThread 4执行
UncaughtExceptionHandler捕获到:customThread 4发生异常/ by zero
可见,结果并不是我们想象的那样,线程池中原有的线程没有复用!所以通过UncaughtExceptionHandler
想将异常吞掉使线程复用这招貌似行不通。它只是做了一层异常的保底处理。
推荐一个 Spring Boot 基础教程及实战示例: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() + "发生异常" + 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() + "执行");
int j = 1 / 0;
});
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
}
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
线程customThread 0执行
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
通过submit提交线程可以屏蔽线程中产生的异常,达到线程复用。当get()执行结果时异常才会抛出。
原因是通过submit提交的线程,当发生异常时,会将异常保存,待future.get();
时才会抛出。最新面试题整理好了,点击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();
}
}
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);
}
总结
本文链接:https://blog.csdn.net/weixin_37968613/article/details/108407774
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
— 【 THE END 】— 本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取! 最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 PDF 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)