记一次使用线程池出现的问题(线程池异常)
背景
之前在工作中遇到一个问题,我定义了一个线程池来执行任务,程序执行结束后任务没有全部执行完。
业务场景是这样的:由于统计业务需要,订单信息需要从主库中经过统计业务代码写入统计库。由于代码质量及历史原因,目前的重新统计接口是单线程的,粗略算了算一共有100万条订单信息,每100条的处理大约是10秒,所以理论上处理完全部信息需要28个小时,这还不算因为 mysql 中 limit 分页导致的后期查询时间以及可能出现的内存溢出导致中止统计的情况。
基于上述的原因,以及最重要的一点:统计业务是根据订单所属的中心进行的,各个中心同时统计不会导致脏数据。所以,我计划使用线程池,为每一个中心分配一条线程去执行统计业务。业务实现// 线程工厂,用于为线程池中的每条线程命名 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("stats-pool-%d").build(); // 创建线程池,使用有界阻塞队列防止内存溢出 ExecutorService statsThreadPool = new ThreadPoolExecutor(5, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100), namedThreadFactory); // 遍历所有中心,为每一个centerId提交一条任务到线程池 statsThreadPool.submit(new StatsJob(centerId));
在创建完线程池后,为每一个 centerId 提交一条任务到线程池。在我的预想中,由于线程池的核心线程数为5,最多5个中心同时进行统计业务,将大大缩短100万条数据的总统计时间,于是万分兴奋的我开始执行重新统计业务了。问题
在跑了很久之后,当我查看统计进度时,我发现了一个十分诡异的问题(如下图)。蓝框标出的这条线程是 WAIT 状态,表明这条线程是空闲状态,但是从日志中我看到这条线程并没有完成它的任务,因为这个中心的数据有10万条,但是日志显示它只跑到了一半,之后就再无关于此中心的日志了。
这是什么原因?调试及原因
可以想到的是,这条线程因为某些原因被阻塞了,并且没有继续进行下去,但是日志又没有任何异常信息...
可能有经验的工程师已经知道了原因...
由于个人水平的线程,暂时没有找到原因的我只能放弃使用线程池,乖乖用单线程跑...
幸运的是,单线程跑的任务竟然抛错了(为什么要说幸运?),于是马上想到,之前那条 WAIT 状态的线程可能是因为同样的抛错所以被中断了,导致任务没有继续进行下去。
为什么说幸运?因为如果单线程的任务没有抛错的话,我可能很久都想不到是这个原因。深入探究线程池的异常处理
工作上的问题到这里就找到原因了,之后的解决过程也十分简单,这里就不提了。
但是疑问又来了,为什么使用线程池的时候,线程因异常被中断却没有抛出任何信息呢?还有平时如果是在 main 函数里面的异常也会被抛出来,而不是像线程池这样被吞掉。
如果子线程抛出了异常,线程池会如何进行处理呢?
我提交任务到线程池的方式是: threadPoolExecutor.submit(Runnbale task); ,后面了解到使用 execute() 方式提交任务会把异常日志给打出来,这里研究一下为什么使用 submit 提交任务,在任务中的异常会被"吞掉"。
对于 submit() 形式提交的任务,我们直接看源码:public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); // 被包装成 RunnableFuture 对象,然后准备添加到工作队列 RunnableFuture ftask = newTaskFor(task, null); execute(ftask); return ftask; }
它会被线程池包装成 RunnableFuture 对象,而最终它其实是一个 FutureTask 对象,在被添加到线程池的工作队列,然后调用 start() 方法后, FutureTask 对象的 run() 方法开始运行,即本任务开始执行。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); } } finally { runner = null; int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
在 FutureTask 对象的 run() 方法中,该任务抛出的异常被捕获,然后在setException(ex); 方法中,抛出的异常会被放到 outcome 对象中,这个对象就是 submit() 方法会返回的 FutureTask 对象执行 get() 方法得到的结果。但是在线程池中,并没有获取执行子线程的结果,所以异常也就没有被抛出来,即被"吞掉"了。
这就是线程池的 submit() 方法提交任务没有异常抛出的原因。线程池自定义异常处理方法
在定义 ThreadFactory 的时候调用setUncaughtExceptionHandler方法,自定义异常处理方法。 例如:ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("judge-pool-%d") .setUncaughtExceptionHandler((thread, throwable)-> logger.error("ThreadPool {} got exception", thread,throwable)) .build();
这样,对于线程池中每条线程抛出的异常都会打下 error 日志,就不会看不到了。后续
在修复了单个线程任务的异常之后,我继续使用线程池进行重新统计业务,终于跑完了,也终于完成了这个任务。
小结:使用线程池时需要注意,子线程的异常,如果没有被捕获就会丢失,可能会导致后期根据日志调试时无法找到原因。
作者:Planeswalker23
链接:https://juejin.im/post/5ed07167f265da7714710d46
宋PLUS新能源享受补贴送大礼包2022年4月28日2022年4月28日,比亚迪海洋盛世新景灿邦新店宋PLUS新能源享受补贴送大礼包,感兴趣的朋友可以到店咨询购买,具体优惠信息如下1。焕新礼首付15起2。臻选礼购
iperf3网络性能测试工具及完整版教程1,iperf3简介iPerf3是用于主动测试IP网络上最大可用带宽的工具。它支持时序缓冲区协议(TCP,UDP,SCTP与IPv4和IPv6)有关的各种参数。对于每次测试,它都会
闲置的手机不要浪费,放在公司实现手机远程打卡真的香由于手机市场的饱和状态以及竞争激烈程度,促使手机更新迭代的速度非常快,导致我国的闲置手机数量逐年增长。据统计我国2015年手机回收总量为5098万部,2020年手机回收总量为170
收到奇怪短信?中国移动中国联通回应来了每经编辑王月龙,易启江据央视新闻报道,昨日晚间开始,有不少网友反映,收到奇怪的短信。短信内容是一连串1234567890的重复数字,收到奇怪短信有136137号段发送方则基本都是1
我国电子商务领域知识产权大保护格局正形成来源光明网光明日报中国电子商务知识产权发展研究报告(2021)发布我国电子商务领域知识产权大保护格局正形成光明日报北京4月28日电(记者张亚雄)国家知识产权局知识产权发展研究中心2
祥鑫科技新能源汽车业务占比将逐步提升中证网讯(记者董添)近日,祥鑫科技接受机构调研时指出,目前公司已为广汽埃安蔚来汽车比亚迪小鹏汽车等新能源汽车供应车身结构件为宁德时代国轩高科亿纬锂能塔菲尔孚能科技欣旺达远景动力巨湾
央行设立2000亿再贷款,强化金融支持科技创新4月28日,人民银行官方发布消息称,为贯彻落实党中央国务院决策部署,强化国家战略科技力量,推进关键核心技术攻关和自主创新,根据国务院常务会议要求,人民银行设立科技创新再贷款。人民银
2022年4月29日币圈晨间新闻一分钟速览关键词俄罗斯沃尔玛谷歌CEODior贝莱德香港RoninConvex1。俄罗斯预计将于5月推出规范加密货币的法案2。沃尔玛成立探索元宇宙和Web3的创新部门3。前谷歌CEO已购买少
mybatis多条件批量删除整理批量增加insertidbatchSaveUserinsertintotuser(username,sex)values!动态SQL之foreach的用法!collectionus
苹果宣布iPhone13Pro系列增产1000万部,同时下架这些应用根据韩媒最新报道称,由于iPhone13系列销量火爆,苹果将原先计划在第二季度生产的iPhone13Pro系列增加至1000万部。据业内人士透露,原先苹果计划在第二季度生产100万
苹果终于推出iPhone自助维修服务,可以自行购买电池和屏幕维修iPhone自助维修上线苹果在近日正式上线了官方自助维修服务,部分地区的用户可以直接在官网购买维修配件,租借维修工具等,对自己的iPhone进行维修。和线下拿去维修一样,苹果提供的