深圳正规制作网站,软件搭建平台,大同市住房城乡建设网站,韩顺平 开源网站来源#xff1a;一个线程池中的线程异常了#xff0c;那么线程池会怎么处理这个线程? - 反光的小鱼儿 - 博客园
一个线程池中的线程异常了#xff0c;那么线程池会怎么处理这个线程? 目录
线程池常用问题 不允许使用的原因测试流程 测试用例抛出堆栈异常为啥对了一半?怎…来源一个线程池中的线程异常了那么线程池会怎么处理这个线程? - 反光的小鱼儿 - 博客园
一个线程池中的线程异常了那么线程池会怎么处理这个线程? 目录
线程池常用问题 不允许使用的原因测试流程 测试用例抛出堆栈异常为啥对了一半?怎么拿到submit的异常堆栈源码查看 执行executes方法时执行submit方法时不影响其他线程任务这个线程会被放回线程池为啥错了结论 1、当执行方式是execute时,可以看到堆栈异常的输出2、当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时可以捕获到异常3、不会影响线程池里面其他线程的正常执行4、线程池会把这个线程移除掉并创建一个新的线程放到线程池中源码执行流程 execute源码执行流程submit源码执行流程一个线程池中的线程异常了那么线程池会怎么处理这个线程?
回到顶部
线程池常用问题
了解JDK Executors线程池吗? 知道JDK提供了哪些默认的实现吗 看过阿里巴巴java开发手册吗知道为啥不允许使用默认的实现吗 你们没有用默认的吧?那来介绍一下你们自定义线程池的几个常用参数呗 你这个几个参数的值是怎么得来的呀算出来的怎么算出来的 线程池里面的任务是IO密集型的还是计算密集型的呢 好现在我们有一个自定义线程池了来说一下你这个线程池的工作流程呗 那你这个线程池满了怎么办呀拒绝咋拒绝有哪些拒绝策略呢 别紧张,随便说两个就行。 ...... 回到开始说的阿里巴巴java开发手册不允许使用默认实现你回答说可能会引起OOM,那我们聊聊JVM吧 不允许使用的原因
线程池不允许使用Executors去创建而是通过ThreadPoolExecutor的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险。 说明Executors各个方法的弊端 1newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存甚至OOM。 2newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE可能会创建数量非常多的线程甚至OOM。
回到顶部
测试流程 测试用例
正在上传…重新上传取消
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;public class ExecutorsTest {public static void main(String[] args) throws Exception {ThreadPoolTaskExecutor executor init();executor.execute(() - sayHi(execute));Thread.sleep(1000);executor.submit(() - sayHi(submit));}public static void sayHi(String name) {String printStr thread-name: Thread.currentThread().getName() ,执行方式 name;System.out.println(printStr);throw new RuntimeException(printStr error!!!);}private static ThreadPoolTaskExecutor init() {ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor();executor.setThreadNamePrefix(thread_);executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(1000);executor.setKeepAliveSeconds(30);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();;return executor;}
}
正在上传…重新上传取消 抛出堆栈异常为啥对了一半? 从执行结果我们看出
当执行方式是execute时,可以看到堆栈异常的输出。 当执行方式是submit时,堆栈异常没有输出。 怎么拿到submit的异常堆栈 所以现在知道为什么回答:抛出堆栈异常只对了一半吧。 execute方法执行时会抛出(打印)堆栈异常。 submit方法执行时返回结果封装在future中如果调用future.get()方法则必须进行异常捕获从而可以抛出(打印)堆栈异常。 你以为这一部分写到这里就完事了那不行啊你心里没有一个疑问吗为啥execute直接抛出异常submit没有直接抛出异常呢
回到顶部
源码查看 执行executes方法时
在java.util.concurrent.ThreadPoolExecutor#runWorker中抛出了异常: 在_java.lang.ThreadGroup#uncaughtException_进行了异常处理: 这个uncaughtException是何许人也,看java doc上咋说的 这个方法是JVM调用的我们只需要指定我们想要的处理方式即可。 那我们怎么指定呢:
正在上传…重新上传取消
//直接new Thread()的时候
Thread tnewThread();
t.setUncaughtExceptionHandler(newThread.UncaughtExceptionHandler()
{public void uncaughtException(Thread t, Throwable e){//根据业务场景做你想做的 }
});
//线程池的时候
ExecutorService threadPool Executors.newFixedThreadPool(1, thread - {
Thread t newThread(thread);
t.setUncaughtExceptionHandler((t1, e) -
System.out.println(根据业务场景做你想做的: e.getMessage()));return;}
);
正在上传…重新上传取消 执行submit方法时 其本质也是调用了execute方法所以它还是回到_java.util.concurrent.ThreadPoolExecutor#runWorker_方法: 向前继续跟进去看看 _java.util.concurrent.FutureTask#setException_干啥了啊瞅一眼 我们马上走向最终的真相: 好了第一个议题【抛出堆栈异常为啥对了一半?】讨论完毕。在源码里面走了一趟现在我们可以给出这一部分的满分答案了。 不影响其他线程任务
这一部分我们直接上代码运行起来看结果吧: 代码和运行结果是不会骗人的: 线程池中一个线程异常了后不影响其他线程任务 大家注意线程名称这个细节:1,2,3,4,6。魔鬼都在细节里啊这个点我下面会讲先在这里把问题抛出来:我就纳闷了,怎么没有5啊?! 这个线程会被放回线程池为啥错了 5号线程去哪里了? new Worker()方法会告诉你:5去哪里了。 再配上这张由我这个灵魂画师亲自操刀画的图一起食用味道更佳 现在知道为啥我回答这个线程会被放回线程池为啥全错了吧。还附带送你一个线程名称变化的细节。
回到顶部
结论
当一个线程池里面的线程异常后: 1、当执行方式是execute时,可以看到堆栈异常的输出
原因ThreadPoolExecutor.runWorker()方法中task.run()即执行我们的方法如果异常的话会throw x;所以可以看到异常。 2、当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时可以捕获到异常
原因ThreadPoolExecutor.runWorker()方法中task.run()其实还会继续执行FutureTask.run()方法再在此方法中c.call()调用我们的方法 如果报错是setException()并没有抛出异常。当我们去get()时会将异常抛出。 3、不会影响线程池里面其他线程的正常执行 4、线程池会把这个线程移除掉并创建一个新的线程放到线程池中
当线程异常会调用ThreadPoolExecutor.runWorker()方法最后面的finally中的processWorkerExit()会将此线程remove并重新addworker()一个线程。
回到顶部
源码执行流程 execute源码执行流程
1、开始执行任务新增或者获取一个线程去执行任务(比如刚开始是新增coreThread去执行任务)。执行到task.run()时会去执行提交的任务。 如果任务执行失败或throw x抛出异常。 2、之后会到finally中的afterExecute()扩展方法我们可以扩展该方法对异常做些什么。 3、之后因为线程执行异常会跳出runWorker的外层循环进入到processWorkerExit()方法此方法会将执行任务失败的线程删除并新增一个线程。 4、之后会到ThreadGroup#uncaughtException方法进行异常处理。 如果没有通过setUncaughtExceptionHandler()方法设置默认的UncaughtExceptionHandler就会在uncaughtException()方法中打印出异常信息。 submit源码执行流程
1、将传进来的任务封装成FutureTask同样走execute的方法调用然后直接返回FutureTask。 2、开始执行任务新增或者获取一个线程去执行任务(比如刚开始是新增coreThread去执行任务)。 3、执行到task.run()时因为是FutureTask所以会去调用FutureTask.run()。 4、在FutureTask.run()中c.call()执行提交的任务。如果抛出异常并不会throw x而是setException()保存异常。 5、当我们阻塞获取submit()方法结果时get()才会将异常信息抛出。当然因为runWorker()没有抛出异常所以并不会删除线程。