点击蓝字关注我吧
1
嘿,朋友,好久不见,我是阿呆。
今天看到一道有趣的面试题:
一个线程池中的线程异常了,那么线程池会怎么处理这个线程?
线程池平时也在用,但是这个问题还真是没怎么研究过,来吧,分析一波。
先来猜一下,大概会出现什么情况
1. 抛出堆栈异常? ---这句话对了一半! 2.不影响其他线程任务? ---这句话全对! 3.这个线程会被放回线程池?---这句话全错!
那到底是什么样的呢?写段代码测试一下啦。。。。
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调用的,我们只需要指定我们想要的处理方式即可。那我们怎么指定呢:
//直接new Thread()的时候
Thread t = new Thread();
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
//根据业务场景,做你想做的
}
});
//线程池的时候:
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技术