70%人答不全!线程池中的一个线程异常了会被怎么处理?

线程池中的一个线程异常了会被怎么处理?

估计很多人会是以下三点答案(me too):

1.抛异常出来并打印在控制台上

2.其他线程任务不受影响

3.异常线程会被回收

但是这里我先提前说一下以上三点不全对,下面我们来具体分析一下。

话不多说用代码来证明

熟悉Executors线程池(本文线程池都是指Executors)都知道 有两种提交线程的方式execute和submit方式,下面将以这两种提交方式来验证。

贴个代码凑个数

public static void main(String[] args) {
ThreadPoolTaskExecutor executorService = buildThreadPoolTaskExecutor();
executorService.execute(() -> run("execute方法"));
executorService.submit(() -> run("submit方法"));
}

private static void run(String name) {
String printStr = "【thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name+"】";
System.out.println(printStr);
throw new RuntimeException(printStr + ",出现异常");
}

private static ThreadPoolTaskExecutor buildThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
executorService.setThreadNamePrefix("(小罗技术笔记)-");
executorService.setCorePoolSize(5);
executorService.setMaxPoolSize(10);
executorService.setQueueCapacity(100);
executorService.setKeepAliveSeconds(10);
executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executorService.initialize();
return executorService;
}

20200425001.png

观看执行结果,诶好奇怪

execute执行方式抛出异常显示在控制台了。

submit执行方式啥都没有输出。

众所周知submit底层其实也是调用的execute,因此它也有异常只是处理方法不一样,它们的区别是:

1、execute没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口

2、submit返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口

那怎么拿到submit中的异常呢?还是用代码来说话

Future<?> result=executorService.submit(() -> run("submit方法"));
try {
result.get();
}catch (Exception e){
e.printStackTrace();
}

20200425002.jpg

获取了一下submit方法的返回结果,发现有异常了和execute一样了,所以第一点抛异常出来并打印在控制台上不是全对的!

到这估计大家和我一样都有一个疑问了 ,为啥execute直接抛出异常,submit没有呢?

要知道这个答案就只能去翻源码看了

在java.util.concurrent.ThreadPoolExecutor#runWorker中抛出了运行异常:
20200425003.png

在java.lang.ThreadGroup#uncaughtException进行了异常处理:
20200425004.png

uncaughtException是什么,我也不知道,百度了一下说这个方法是JVM调用的,在线程中只需要指定我们想要的处理方式即可
20200425005.png

说道这里你可能会吐槽说了这么多submit到底为啥没有直接抛出异常,到底是怎么处理了,不要慌我们再看源码找答案
20200425006.png

看submit源码会发现,submit中传进来的task会被封装成一个FutureTask,然后再调用execute,最后返回FutureTask。
20200425007.png

你会发现走的是execute方法,如下图,会发现此时的task已经是FutureTask,所以再去看一下FutureTask的run方法是咋写的。
20200425008.png

异常被存起来了…,再看源码是怎么实现的
20200425009.png

返回参数里面有一个状态state翻源码发现在report方法中同时用到outcom和state状态判断打个断点发现还真是,当state为3时抛出了异常。
20200425010.png

同理在找寻一下report调用位置可以很明显发现是FutureTask中get方法调用了,结合上面可以很明确了submit提交时异常被存储在线程结果信息中,当调用get方法是判断线程运行结果状态,有异常就抛出存储的异常信息,因此submit运行异常我们只能用get方法来拿到。

至于第二点我就不多说了,平时使用中就已经证明了!

第三点线程出异常了不是被线程池回收嘛?

看源码我们知道线程运行最后总有一个processWorkerExit要执行,看看里面的实现

20200425011.png

很神奇先删掉线程又再调用创建线程的方法,所以异常线程不是被回收,而是被删除了再创建一个新的顶替了。

到此线程池中的线程异常了会被怎么处理讲完了,总结一下就是:

1、execute方法,可以看异常输出在控制台,而submit在控制台没有直接输出,必须调用Future.get()方法时,可以捕获到异常。

2、一个线程出现异常不会影响线程池里面其他线程的正常执行。

3、线程不是被回收而是线程池把这个线程移除掉,同时创建一个新的线程放到线程池中。

4、还有源码是个好东西,答案都在里面,就是太难看懂了

xuanchuantu.png

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值