最新要闻
- 世界观热点:张奔斗:阳光双赛双四强 辛纳状态很“辛辣”
- 今日讯!男孩向女生水杯里放吸铁石 差点被喝下:医生称严重会胃穿孔
- 法国法芙娜脆珠+新鲜奶油 上行斋生巧福团狂促:8枚券后不到15元
- 精选!罗永浩将在淘宝直播间售卖民用卫星:最低200万起步
- ChatGPT也要带货?微软开始在新必应插入广告
- 环球微头条丨长期进食柿子山楂 胃中长出巨大“顽石” 医生巧用无糖可乐辅助尿毒症女子成功碎石
- 年轻人找工作不看工资看什么?一季度全国平均招聘月薪10101元 学历越高钱越多
- 世界新消息丨眼科医生自己真的都不做近视手术?
- 世界观天下!周上险量超5000 理想L系累计交付超10万台:其他新势力难了
- 热门美剧推动PS5销量大涨
- 专家提醒:千万不要侧躺玩手机 科普标准姿势
- 退役军人事务部调研组来洛 常正国带队
- 厚衣服先别收!强冷空气很快就到:今年来最大范围雨雪
- “基尼太美” 兰博基尼Revuelto亮相 马力超千匹 2.5秒破百
- 【独家焦点】Win12 UI设计曝光被苹果用户吐槽:抄袭macOS太明显
- 2022年房屋的主要卖点是什么
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
快看点丨京东二面:线程池中的线程抛出了异常,该如何处理?大部分人都会答错!
在实际开发中,我们常常会用到线程池,但任务一旦提交到线程池之后,如果发生异常之后,怎么处理? 怎么获取到异常信息?
在了解这个问题之前,可以先看一下 线程池的源码解析,从源码中我们知道了线程池的提交方式:submit和execute的区别,接下来分别使用他们执行带有异常的任务!看结果是怎么样的!
我们先用伪代码模拟一下线程池抛异常的场景:
(资料图片仅供参考)
public class ThreadPoolException { public static void main(String[] args) { //创建一个线程池 ExecutorService executorService= Executors.newFixedThreadPool(1); //当线程池抛出异常后 submit无提示,其他线程继续执行 executorService.submit(new task()); //当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务 executorService.execute(new task()); }}//任务类class task implements Runnable{ @Override public void run() { System.out.println("进入了task方法!!!"); int i=1/0; }}
运行结果:
可以看到:submit不打印异常信息,而execute则会打印异常信息!,submit的方式不打印异常信息,显然在生产中,是不可行的,因为我们无法保证线程中的任务永不异常,而如果使用submit的方式出现了异常,直接如上写法,我们将无法获取到异常信息,做出对应的判断和处理,所以下一步需要知道如何获取线程池抛出的异常!
submit()
想要获取异常信息就必须使用get()
方法!!
//当线程池抛出异常后 submit无提示,其他线程继续执行Future> submit = executorService.submit(new task());submit.get();
submit打印异常信息如下:
推荐一个开源免费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
方案一
使用
try -catch
public class ThreadPoolException { public static void main(String[] args) { //创建一个线程池 ExecutorService executorService = Executors.newFixedThreadPool(1); //当线程池抛出异常后 submit无提示,其他线程继续执行 executorService.submit(new task()); //当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务 executorService.execute(new task()); }}// 任务类class task implements Runnable { @Override public void run() { try { System.out.println("进入了task方法!!!"); int i = 1 / 0; } catch (Exception e) { System.out.println("使用了try -catch 捕获异常" + e); } }}
打印结果:
可以看到 submit 和 execute都清晰易懂的捕获到了异常,可以知道我们的任务出现了问题,而不是消失的无影无踪。
方案二:
使用
Thread.setDefaultUncaughtExceptionHandler
方法捕获异常。
方案一中,每一个任务都要加一个try-catch
实在是太麻烦了,而且代码也不好看,那么这样想的话,可以用Thread.setDefaultUncaughtExceptionHandler
方法捕获异常
UncaughtExceptionHandler
是Thread类一个内部类,也是一个函数式接口。
内部的uncaughtException
是一个处理线程内发生的异常的方法,参数为线程对象t和异常对象e。
应用在线程池中如下所示:重写它的线程工厂方法,在线程工厂创建线程的时候,都赋予UncaughtExceptionHandler
处理器对象。
public class ThreadPoolException { public static void main(String[] args) throws InterruptedException { //1.实现一个自己的线程池工厂 ThreadFactory factory = (Runnable r) -> { //创建一个线程 Thread t = new Thread(r); //给创建的线程设置UncaughtExceptionHandler对象 里面实现异常的默认逻辑 t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> { System.out.println("线程工厂设置的exceptionHandler" + e.getMessage()); }); return t; }; //2.创建一个自己定义的线程池,使用自己定义的线程工厂 ExecutorService executorService = new ThreadPoolExecutor( 1, 1, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(10), factory); // submit无提示 executorService.submit(new task()); Thread.sleep(1000); System.out.println("==================为检验打印结果,1秒后执行execute方法"); // execute 方法被线程工厂factory 的UncaughtExceptionHandler捕捉到异常 executorService.execute(new task()); }}class task implements Runnable { @Override public void run() { System.out.println("进入了task方法!!!"); int i = 1 / 0; }}
打印结果如下:
根据打印结果我们看到,execute方法被线程工厂factory中设置的 UncaughtExceptionHandler
捕捉到异常,而submit方法却没有任何反应!说明UncaughtExceptionHandler
在submit中并没有被调用。这是为什么呢?
在日常使用中,我们知道,execute和submit最大的区别就是execute没有返回值,submit有返回值。submit返回的是一个future ,可以通过这个future取到线程执行的结果或者异常信息。
Future> submit = executorService.submit(new task());//打印异常结果 System.out.println(submit.get());
从结果看出:submit并不是丢失了异常,使用future.get()
还是有异常打印的!!那为什么线程工厂factory 的UncaughtExceptionHandler
没有打印异常呢?猜测是submit方法内部已经捕获了异常, 只是没有打印出来,也因为异常已经被捕获,因此jvm也就不会去调用Thread的UncaughtExceptionHandler
去处理异常。
接下来,验证猜想。submit源码在底层还是调用的execute方法,只不过多一层Future封装,并返回了这个Future,这也解释了为什么submit会有返回值
//submit()方法 public Future submit(Callable task) { if (task == null) throw new NullPointerException(); //execute内部执行这个对象内部的逻辑,然后将结果或者异常 set到这个ftask里面 RunnableFuture ftask = newTaskFor(task); // 执行execute方法 execute(ftask); //返回这个ftask return ftask; }
可以看到submit也是调用的execute,在execute方法中,我们的任务被提交到了addWorker(command, true)
,然后为每一个任务创建一个Worker去处理这个线程,这个Worker也是一个线程,执行任务时调用的就是Worker的run方法!run方法内部又调用了runworker方法!如下所示:
public void run() { runWorker(this); }final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //这里就是线程可以重用的原因,循环+条件判断,不断从队列中取任务 //还有一个问题就是非核心线程的超时删除是怎么解决的 //主要就是getTask方法()见下文③ while (task != null || (task = getTask()) != null) { w.lock(); if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { //执行线程 task.run(); //异常处理 } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //execute的方式可以重写此方法处理异常 afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } //出现异常时completedAbruptly不会被修改为false completedAbruptly = false; } finally { //如果如果completedAbruptly值为true,则出现异常,则添加新的Worker处理后边的线程 processWorkerExit(w, completedAbruptly); } }
核心就在 task.run();
这个方法里面了, 期间如果发生异常会被抛出。
- 如果用execute提交的任务,会被封装成了一个runable任务,然后进去 再被封装成一个worker,最后在worker的run方法里面调用runWoker方法,
runWoker
方法里面执行任务任务,如果任务出现异常,用try-catch
捕获异常往外面抛,我们在最外层使用try-catch
捕获到了runWoker
方法中抛出的异常。因此我们在execute中看到了我们的任务的异常信息。 - 那么为什么submit没有异常信息呢? 因为submit是将任务封装成了一个
futureTask
,然后这个futureTask
被封装成worker,在woker的run方法里面,最终调用的是futureTask
的run方法, 猜测里面是直接吞掉了异常,并没有抛出异常,因此在worker的runWorker
方法里面无法捕获到异常。
下面来看一下futureTask
的run方法,果不其然,在try-catch中吞掉了异常,将异常放到了 setException(ex);
里面
public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; 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); } //省略下文 。。。。。。setException(ex)`方法如下:将异常对象赋予`outcomeprotected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { //将异常对象赋予outcome,记住这个outcome, outcome = t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }
将异常对象赋予outcome
有什么用呢?这个outcome
是什么呢?当我们使用submit返回Future对象,并使用Future.get()
时, 会调用内部的report方法!
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); //注意这个方法 return report(s);}
reoport里面实际上返回的是outcome ,刚好之前的异常就set到了这个outcome里面
private V report(int s) throws ExecutionException { //设置`outcome` Object x = outcome; if (s == NORMAL) //返回`outcome` return (V)x; if (s >= CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x);}
因此,在用submit提交的时候,runable对象被封装成了future ,future 里面的 run方法在处理异常时, try-catch
了所有的异常,通过setException(ex);
方法设置到了变量outcome里面, 可以通过future.get
获取到outcome。
所以在submit提交的时候,里面发生了异常, 是不会有任何抛出信息的。而通过future.get()
可以获取到submit抛出的异常!在submit里面,除了从返回结果里面取到异常之外, 没有其他方法。因此,在不需要返回结果的情况下,最好用execute ,这样就算没有写try-catch
,疏漏了异常捕捉,也不至于丢掉异常信息。
方案三
重写afterExecute进行异常处理。
通过上述源码分析,在excute的方法里面,可以通过重写afterExecute
进行异常处理,但是注意! 这个也只适用于excute提交(submit的方式比较麻烦,下面说),因为submit的task.run
里面把异常吞了,根本不会跑出来异常,因此也不会有异常进入到afterExecute
里面。
在runWorker
里面,调用task.run之后,会调用线程池的 afterExecute(task, thrown)
方法
final void runWorker(Worker w) {//当前线程 Thread wt = Thread.currentThread(); //我们的提交的任务 Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { //直接就调用了task的run方法 task.run(); //如果是futuretask的run,里面是吞掉了异常,不会有异常抛出, // 因此Throwable thrown = null; 也不会进入到catch里面 } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //调用线程池的afterExecute方法 传入了task和异常 afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
重写afterExecute
处理execute提交的异常
public class ThreadPoolException3 { public static void main(String[] args) throws InterruptedException, ExecutionException { //1.创建一个自己定义的线程池 ExecutorService executorService = new ThreadPoolExecutor( 2, 3, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(10) ) { //重写afterExecute方法 @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println("afterExecute里面获取到异常信息,处理异常" + t.getMessage()); } }; //当线程池抛出异常后 execute executorService.execute(new task()); }}class task3 implements Runnable { @Override public void run() { System.out.println("进入了task方法!!!"); int i = 1 / 0; }}
执行结果:我们可以在afterExecute
方法内部对异常进行处理
如果要用这个afterExecute
处理submit提交的异常, 要额外处理。判断Throwable
是否是FutureTask
,如果是代表是submit提交的异常,代码如下:
public class ThreadPoolException3 { public static void main(String[] args) throws InterruptedException, ExecutionException { //1.创建一个自己定义的线程池 ExecutorService executorService = new ThreadPoolExecutor( 2, 3, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(10) ) { //重写afterExecute方法 @Override protected void afterExecute(Runnable r, Throwable t) { //这个是excute提交的时候 if (t != null) { System.out.println("afterExecute里面获取到excute提交的异常信息,处理异常" + t.getMessage()); } //如果r的实际类型是FutureTask 那么是submit提交的,所以可以在里面get到异常 if (r instanceof FutureTask) { try { Future> future = (Future>) r; //get获取异常 future.get(); } catch (Exception e) { System.out.println("afterExecute里面获取到submit提交的异常信息,处理异常" + e); } } } }; //当线程池抛出异常后 execute executorService.execute(new task()); //当线程池抛出异常后 submit executorService.submit(new task()); }}class task3 implements Runnable { @Override public void run() { System.out.println("进入了task方法!!!"); int i = 1 / 0; }}
处理结果如下:
可以看到使用重写afterExecute
这种方式,既可以处理execute抛出的异常,也可以处理submit抛出的异常。
版权声明:本文为CSDN博主「知识分子_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_45076180/article/details/114552567
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!
关键词:
全球今热点:履约核心引擎低代码化原理与实践
天天快资讯:Linux 修改系统时间的两种方式
快看点丨京东二面:线程池中的线程抛出了异常,该如何处理?大部分人都会答错!
世界观热点:张奔斗:阳光双赛双四强 辛纳状态很“辛辣”
今日讯!男孩向女生水杯里放吸铁石 差点被喝下:医生称严重会胃穿孔
法国法芙娜脆珠+新鲜奶油 上行斋生巧福团狂促:8枚券后不到15元
精选!罗永浩将在淘宝直播间售卖民用卫星:最低200万起步
ChatGPT也要带货?微软开始在新必应插入广告
环球微头条丨长期进食柿子山楂 胃中长出巨大“顽石” 医生巧用无糖可乐辅助尿毒症女子成功碎石
环球百事通!【网络安全软件】上海道宁与Cybereason为您提供未雨绸缪的攻击保护,终结对端点、整个企业以及网络上任何角落的网络攻击
环球速读:1.redis的基本使用
全球简讯:前端设计模式——依赖注入模式
年轻人找工作不看工资看什么?一季度全国平均招聘月薪10101元 学历越高钱越多
世界新消息丨眼科医生自己真的都不做近视手术?
世界观天下!周上险量超5000 理想L系累计交付超10万台:其他新势力难了
热门美剧推动PS5销量大涨
专家提醒:千万不要侧躺玩手机 科普标准姿势
退役军人事务部调研组来洛 常正国带队
天天观热点:Linux的3个文件时间
Cursor,程序员的 AI 代码编辑助手
厚衣服先别收!强冷空气很快就到:今年来最大范围雨雪
“基尼太美” 兰博基尼Revuelto亮相 马力超千匹 2.5秒破百
【独家焦点】Win12 UI设计曝光被苹果用户吐槽:抄袭macOS太明显
2022年房屋的主要卖点是什么
3月份国有大行二级资本债密集发行 专家称大型银行与中小银行发债规模将明显分层
邮储银行450亿元定增落地 中国移动溢价逾40%“通吃”
世界讯息:鹰眼自爆伤情
天天观速讯丨读SQL进阶教程笔记02_三值逻辑和NULL
【全球热闻】结石痛怎么缓解_尿道结石疼痛怎么缓解
汽车雷达在无人陵园内显示全是人影 网友吐槽更智能了:官方回应
观天下!1W字长文:K8S Ingress 原理和实操
简讯:mvc-mvp-mvvm架构调研及实现--分布式课程思考题--zzb
【Visual Leak Detector】配置项 SkipHeapFreeLeaks
Unity 中的存档系统(本地存档)
最新消息:IDEA2023.1破解 永久激活 最新版IDEA激活 亲测可用!
突破10GB/s!影驰首款PCIe 5.0 SSD开卖:2TB售价2499元
热门:2.1s破百!100万的新款特斯拉 快得我差点吐了
世界今亮点!苹果WWDC大会定档6月6日:iOS 17无悬念、MR头显最受期待
达墨国产PCIe 5.0 硬盘2TB 1899元 官方坦诚提醒:谁买谁冤种
当前速看:飞艇事件的背后
世界热文:搞笑动漫日和在线观看(搞笑动漫日和)
天天新资讯:Ubuntu18.04系统安装nginx
天天速递!手撕HashMap(二)
【Visual Leak Detector】配置项 TraceInternalFrames
苹果推出的美国版花呗:被严重低估了
全球聚焦:为什么洗澡时总想“尿尿”?这怀习惯可不好!
环球信息:新一代高贵“亮机卡”!RTX 4050被曝6月发布:弱得不像话
【世界报资讯】 “大号MINI”!五菱缤果正式上市:5.98万元起
国产特有 魔改RTX 3060显卡999元:AMD也做不到的性价比
天天通讯!报告:电信业采购供应链发展呈现四大趋势
【独家】【Visual Leak Detector】配置项 StartDisabled
78.类型转换
环球快报:2023年找工作的心酸历程
全球热议:ASP.NET Core MVC+Quartz实现定时任务可视化管理页面
世界即时看!SSM框架笔记 庆祝学习SSM框架结束!!!
世界速读:【财经分析】债市短期表现向好 机构操作犹存分歧
当前速看:《生化危机4:重制版》阿什莉服装Mod公布 看了把持不住
天天亮点!CPU占用暴降 SSD提速百倍:《暗黑4》将支持微软DX游戏神技
具体如何编写信号与槽
今日热讯:关于大国竞争,“修昔底德陷阱”提出者谈到了“澶渊之盟”
合资轿车雪上加霜!上汽名爵最帅轿跑MG7上市:11.98万起
新动态:ROG游戏手机7跑分首曝:二代骁龙8 134.6万冠绝全球
规模或超90%!流媒体巨头Hulu国内被曝大裁员
JOLED破产 日本OLED面板技术押错宝:弯道超车失败
当前动态:骁龙8cx Gen 4处理器跑分曝光:12核性能大跌眼镜 仅苹果M2 Max 1/3
【世界速看料】76.算术运算符
世界动态:PfSense pfBlockerNG 未授权RCE漏洞(CVE-2022-31814)
热点!Unity中基于EPPLUS的Excel转换以及Json数据读取
全球速讯:c# 对序列化类XMLSerializer 二次封装泛型化方便了一些使用的步骤
新华社权威快报|首次纳入肝素类药品 第八批药品集采平均降价56%
焦点!一排小草怎么画简单好看_一排小草怎么画简笔画
当前快讯:Win12正全力开发!微软重构操作系统底层:模块化设计
【新视野】宣称能跑100公里!男子网购电动车续航打折:法院判退一赔三
全球快资讯:“雪糕刺客”的仁慈!钟薛高用文心一言打造雪糕 仅售3.5元
即时看!口腔溃疡总不好 可能是大病预警!“偏方”都没用!
今日讯!比5G强10倍!工信部:中国已成立6G工作组推动关键技术研究
pthread库实现简单并行程序:Hello
全球焦点!日本央行官员暗示调整YCC政策 日债收益率周三全线回落
全球快资讯:净利润同比增长超62% 中国石油2022年业绩创历史最好水平
全球视点!韩瑟冻干粉怎么样_韩瑟化妆品怎么样
果然来了!电商偷跑索尼PS5 Slim游戏机:新外观 更轻薄
全球热头条丨SSD性能狂飙 追赶DDR5内存 PCIe 6.0硬盘预计2026年问世
听到吧唧嘴就抓狂:一男子已4年不理家人
头条:德国掀桌 欧盟让步 “2035禁燃令”为何不再禁燃油车?
男子测智商竟被推荐花98元包月:付了钱也没看到结果
【独家焦点】郑氏点银:黄金有望震荡冲1980,原油背离逼空反弹待回落
【天天快播报】记录--开局一张图,构建神奇的 CSS 效果
java泛型和通配符
环球微资讯!玩家期待已久!任天堂限定版Switch来了
全球今日报丨不拍蒜也断?张小泉斩骨刀斩骨时断裂 客服:与使用力度、角度有关
车被撞废人完好无损!比亚迪汉DM-i车主转头定了一台海豹
环球快资讯丨为何Redmi敢首发高通第二代骁龙7+?员工解释原因
男子驾车途中昏迷撞走公司大门 罪魁祸首竟是一只马蜂
全球看热讯:惠州治皮肤过敏较好的医院
全球微头条丨用 Go 剑指 Offer 07. 重建二叉树
快资讯:IDEA使用技巧和注册教程
全网最详细中英文ChatGPT-GPT-4示例文档-最强JS助手聊天机器人应用从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/
全球快看点丨下载安装MyAQL数据库8.0.30
NX二次开发:Checkmate例子根据dfa文件检查模型数据
天天即时看!广东佛山发生3.4级地震 广州有震感!你感受到没