范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

记一次使用线程池出现的问题(线程池异常)

  背景
  之前在工作中遇到一个问题,我定义了一个线程池来执行任务,程序执行结束后任务没有全部执行完。
  业务场景是这样的:由于统计业务需要,订单信息需要从主库中经过统计业务代码写入统计库。由于代码质量及历史原因,目前的重新统计接口是单线程的,粗略算了算一共有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

华为海思公布新芯片越影AIISP芯片问世,将夜晚拍成白天尽管华为受到禁令的限制,无法找到芯片代工厂生产一些高端的SoC芯片,但华为的芯片设计之路一直没有停歇下来。华为的海思团队最近频频发布一些新的芯片,前不久才发布了新的视频处理芯片,而现在建一个网站一年费用只要298元,他们玩的是什么套路?做网站多年,这方面还是很清楚的。这些公司都有统一的建站系统,有很多可以选择的模版。建立网站无非就是往里传几篇文章,放一些图片就可以了。不需要每个网站进行开发,成本非常低。域名。CO2022年春节买手机时的三不碰,记住以免上当受骗现在手机已经成为人们生活中离不开电子产品,市面上的手机也是各种类型都让人眼花缭乱,但买手机记住三不碰发以免上当受骗,买到那些性价比低的智商税手机千人以下的低端机,除非买给老人或者小为什么懂行的人不再买128G的手机了?听听懂行人怎么说不同内存的智能手机购买价格不同,内存越大,手机价格越贵,为什么很多人宁愿多花几百块也要买256G内存的?为什么现在稍微懂行的人都不推荐128G的手机了呢?想要使用时间长,就选大内存这是华为首款竖折手机P50Pocket华为P50Pocket年关将至,中国手机市场却是出奇地热闹。各家你方唱罢我登场,今天就轮到华为来推出自己的第一款竖折萤幕手机P50Pocket。在之前的预热中,我们就可以看到这款选8部手机续航快充测试iPhone13ProMax依然优秀,没有让果粉失望手机续航一直都是消费者非常关注的,因为手机续航能力的大小直接影响到消费者的使用体验,所以各大手机厂商也都在电池的续航和以及快充方面下功夫,希望自家的产品能够给消费者带来非常好的体验消息称小米Redmi还将推出骁龙870新手机IT之家12月11日消息,到目前为止,高通已经发布了骁龙8Gen1旗舰芯片,联发科发布了天玑9000芯片,不过对于年底或者明年发布的智能手机新品来说,骁龙870仍然是一款不错的芯片第三期1000块买一款二手的865手机值不值得?前言之前写了一加7pro,有人留言要推荐一下865手机。今天他来了!依照我的尿性,我是能省钱就省钱,除非万不得已!贫穷想要865又想要便宜,那只有不搭载高刷的屏幕的手机才符合要求!龙芯CPU再进一步,国产CPU们追上英特尔还需多久?电子发烧友网报道(文黄山明)近日,据中科院科技创新亮点成果筛选活动页面的结果显示,龙芯中科技术股份有限公司已经完成了基于自主LoongArch指令系统的龙芯3C5000服务器CPU苹果iPhoneSE3最新曝光,2500就能拿下了?文机sir相信有一直关注机sir至今的果粉们应该都明白了,在当前的5G手机市场,苹果可以说是最大的赢家,虽然截至目前,苹果只发布了两款5G机型,分别是iPhone12和iPhone据称苹果聘请了Meta的AR公关负责人根据彭博记者MarkGurman在最新一期新闻通讯中所提到的,据称苹果聘请了Meta的AR产品传讯与公共关系负责人AndreaSchubert。Schubert的LinkedIn页
iPhone13必备!iWALKmagsafe磁吸充电宝上手体验,真方便作为一个数码爱好者,之前用过各种各样的充电宝,但是对于magsafe我一直敬而远之,最主要的原因原装的太贵了,第三方的充电功率又不行,因此迄今也没有真正意义上的体验过。最近,iWA37Style标签写在body后与body前有什么区别?(了解)区别1写在body标签前利于浏览器逐步渲染resourcesdownloadingCSSOMDOMRenderTree(composite)Layoutpaint2写在body标签一台两年前的华为Mate30Pro放现在,相当于什么价位的手机?华为Mate系列的手机主打的都是商务旗舰机,回顾整个2019年,提起最高端的一款国产旗舰手机,相信很多小伙伴都会想到华为Mate30Pro。那一台两年前256G版本的华为Mate3华为P60还是来了,或将带来最新科技华为P60终于还是来了,据传将于冬季发布,搭载麒麟新处理器!将采用完全自主设计自主制造的新麒麟芯片,支持5G,国产14nm叠层封装工艺,性能并不落后,但是成本很高,GPU也是华为自入手了一款便携的低音小钢炮,SanagM11蓝牙音箱浅谈如今,很多人都会用音乐来调节心情,除此之外,还可以在聚会和旅行时让音乐陪伴我们。随着科技的发展,现在越来越多各种款式的音箱出现在我们的生活中,不仅价格便宜还携带方便。最近V叔就看中黄金和比特币,你会收藏哪个?比特币。因为第一,比特币便于携带。它无论是热钱包,还是冷钱包都很便于携带。黄金就不同了,太多了,你搬着都费劲啊。第二,便于适用。比特币支付你直接扫码就可以了。黄金就麻烦多了,难道你小白创业做什么好?创业说难也难,说不难也不难,当机遇来临的时候,所谓站在风口,猪都能够飞起来,更何况我们有智慧的人呢。小编给你几点建议,但是具体的还是要靠你自己,毕竟是自己创业,自己赚钱,这一步没人数码圈这一年,自研自强数码圈这一年2021年即将结束,在这一年里数码圈也发生了很多事,有喜,也有忧。1鸿蒙系统问世,全球第三大操作系统!2集体进军自研,科技为本,拒绝卡脖子!3华为跌倒,集体发力,高端市年底最值得购买的3款旗舰机,iPhone13依旧上榜再过两天,就到元旦了,距离春节还会远么?这个时候,相信很多人都有购买年货的需求,其中之一就是智能手机了。从目前在售的5G手机来看,选择有很多,可以说是让人眼花缭乱,甚至还在纠结买不买了两台冰箱,终于搞清楚了两千多和一万多的冰箱有什么区别今年夏天的时候我需要买一台冰箱,正好赶上爸妈家的冰箱也坏掉了,于是让我一块买了。爸妈辛苦了一辈子,手头自然比我宽裕得多。于是他们选了一台一万元出头的高端冰箱,而我选了一台两千多几乎两极反转!周鸿祎竟然开始拥抱元宇宙,把元宇宙分五派!三六零是哪一派?今天就不喷元宇宙了,而是要把元宇宙引导到对国家和产业有利的方向发展,为真实世界服务。在28日举行的360集团战略发布会上,三六零创始人周鸿祎对元宇宙态度实现180度转弯。如今的周鸿