前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >有人关心过线程池里死掉的线程吗?

有人关心过线程池里死掉的线程吗?

作者头像
Java阿呆
发布2021-04-30 16:08:19
1.5K0
发布2021-04-30 16:08:19
举报
文章被收录于专栏:Java阿呆Java阿呆

点击蓝字关注我吧

1

嘿,朋友,好久不见,我是阿呆。

今天看到一道有趣的面试题:

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

线程池平时也在用,但是这个问题还真是没怎么研究过,来吧,分析一波。

先来猜一下,大概会出现什么情况

1. 抛出堆栈异常? ---这句话对了一半! 2.不影响其他线程任务? ---这句话全对! 3.这个线程会被放回线程池?---这句话全错!

那到底是什么样的呢?写段代码测试一下啦。。。。

代码语言:javascript
复制
public class ExecutorsTest {

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

    private static void sayHi(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("Coder阿呆-");
        executorService.setCorePoolSize(5);
        executorService.setMaxPoolSize(10);
        executorService.setQueueCapacity(1000);
        executorService.setKeepAliveSeconds(30);
        executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executorService.initialize();
        return executorService;
    }
}

先让程序跑起来,看看效果:

从执行结果可以看出:

当执行方式是execute时,可以看到堆栈异常的输出。 当执行方式是submit时,堆栈异常没有输出。

这也就是为什么说,抛出异常堆栈,不完全对了。

那为什么以submit方式执行的没有打印堆栈呢,或者说怎么拿到这个堆栈呢?

到这可以看到,通过submit方式执行时会返回Future结果,调用结果的get()方法,才会把异常信息打印出来,所以总结一下:

execute方法执行时,会抛出(打印)堆栈异常。 submit方法执行时,返回结果封装在future中,如果调用future.get()方法则必须进行异常捕获,从而可以抛出(打印)堆栈异常。

那么问题来了,为啥execute可以直接抛出异常,submit却没有直接抛出异常呢?这就要去源码寻找答案咯~(正所谓源码之下无秘密)

执行方式为execute时,答案在java.util.concurrent.ThreadPoolExecutor#runWorker:

可以看到在runWorker方法中直接对异常重新抛出,并在java.lang.ThreadGroup#uncaughtException进行了异常处理:

uncaughtException这个方法是JVM调用的,我们只需要指定我们想要的处理方式即可。那我们怎么指定呢:

代码语言:javascript
复制
//直接new Thread()的时候
Thread t = new Thread();
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
      //根据业务场景,做你想做的
  }
});
代码语言:javascript
复制
//线程池的时候:
ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(
   (t1, e) -> System.out.println("根据业务场景,做你想做的:" + e.getMessage()));
      return t;
});

好了到这里,通过execute方式执行的的情况,基本已经真相大白了,那么再来看一下submit方式:

可以看到,submit方法将入参task包装成了一个FutureTask,然后也调用了execute方法,只不过立刻把futureTask返回了而已, 所以再回到java.util.concurrent.ThreadPoolExecutor#runWorker方法,但需要注意的是,此时的task已经被包装成了FutureTask,所以调用run方法时,需要看FutureTask的run方法,直接看:

其中的call方法就是我们传进来的方法,所以肯定会抛异常,但是这个异常是怎么处理的呢,被catch掉了,并且没有再抛出来,而是被保存了下来,保存到哪里?当然是FutureTask里,所以调用get()方法时才会抛出异常,而且强制要求调用get()方法时处理异常。

你可能还想问,setException()方法到底干了啥?看一下不就知道了嘛:

诺,就这么简单。

简单?一点都不简单!这里才是最终的真相!

这个方法将线程状态流转到EXCEPTIONAL,并将state变量设置为3(异常状态),然后结束了线程,而调用FutureTask的get()方法时,判断的就是state的值,看一下:

什么?没有看到数字3?诺~

猜测一证明完毕。

猜测二三明天再证明,顺便详细总结一下这个状态流转的问题。

本文参考内容:why技术

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coder阿呆 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档