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

看起来是线程池的BUG,但是我认为是源码设计不合理

  前几天看到一个 JDK 线程池的 BUG,我去了解了一下,摸清楚了它的症结所在之后,我觉得这个 BUG 是属于一种线程池方法设计不合理的地方,而且官方在知道这个 BUG 之后表示:确实是个 BUG,但是我就不修复了吧,你就当这是一个 feature 吧。
  在带你细嗦这个 BUG 之前,我先问一个问题:
  JDK 自带的线程池拒绝策略有哪些?
  这玩意,老八股文了,存在的时间比我从业的时间都长,得张口就来: AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常,这是默认的策略。 DiscardOldestPolicy:丢弃队列最前面的任务,执行后面的任务 CallerRunsPolicy:由调用线程处理该任务 DiscardPolicy:也是丢弃任务,但是不抛出异常,相当于静默处理。
  这次的这个 BUG 触发条件之一,就藏着在这个 DiscardPolicy 里面。
  但是你一去看源码,这个玩意就是个空方法啊,这能有什么 BUG?
  它错就错在是一个空方法,把异常给静默处理了。
  别急,等我慢慢给你摆。
  啥BUG啊?
  BUG 对应的链接是这个:
  https://bugs.openjdk.org/browse/JDK-8286463
  标题大概就是说:噢,我的老伙计们,听我说,我发现线程池的拒绝策略 DiscardPolicy 遇到 invokerAll 方法的时候,可能会导致线程一直阻塞哦。
  然后在 BUG 的描述部分主要先注意这两段:
  这两段透露出两个消息: 1.这个 BUG 之前有人提出来过。 2.Doug 和 Martin 这两位也知道这个 BUG,但是他们觉得用户可以通过编码的方式避免永远阻塞的问题。
  所以我们还得先去这个 BUG 最先出现的地方看一下。也就是这个链接:
  https://bugs.openjdk.org/browse/JDK-8160037
  从标题上来看,这两个问题非常的相似,都有 invokerAll 和 block,但是触发的条件不一样。
  一个是 DiscardPolicy 拒绝策略,一个是 shutdownNow 方法。
  所以我的策略是先带你先把这个 shutdownNow 方法嗦明白了,这样你就能更好的理解 DiscardPolicy 带来的问题。
  本质上,它们说的是一回事儿。 现象
  在 shutdownNow 相关的这个 BUG 描述里面,提问者给到了他的测试用例,我稍微改改,就拿来就用了。
  https://bugs.openjdk.org/browse/JDK-8160037
  代码贴在这里,你也可以那到你本地跑一下: public class MainTest {      public static void main(String[] args) throws InterruptedException {                  List> tasks = new ArrayList<>();         for (int i = 0; i < 10; i++) {             int finalI = i;             tasks.add(() -> {                 System.out.println("callable "+ finalI);                 Thread.sleep(500);                 return null;             });         }          ExecutorService executor = Executors.newFixedThreadPool(2);         Thread executorInvokerThread = new Thread(() -> {             try {                 executor.invokeAll(tasks);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println("invokeAll returned");         });         executorInvokerThread.start();     } }
  然后给大家解释一下测试代码是在干啥事儿。
  首先标号为 ① 的地方,是往 list 里面塞了 10 个 callable 类型的任务。
  搞这么多任务干啥呢?
  肯定是要往线程池里面扔,对吧。
  所以,在标号为 ② 的地方,搞了一个线程和核心线程数是 2 的线程池。在线程里面调用了线程池的 invokerAll 方法:
  这个方法是干啥的?
  Executes the given tasks, returning a list of Futures holding their status and results when all complete.
  执行给定的任务集合,在所有任务完成后返回一个包含其状态和结果的 Futures 列表。
  也就是说,当线程启动后,线程池会把 list 里面的任务一个个的去执行,执行完成后返回一个 Futures 列表。
  我们写代码的时候拿着这个列表就能知道这一批任务是否都执行完成了。
  但是,朋友们,但是啊,注意一下,你看我的案例里面根本就不关心 invokerAll 方法的返回值。
  关心的是在 invokerAll 方法执行完成后,输出的这一句话:
  invokeAll returned
  好,现在你来说这个程序跑起来有什么毛病?
  你肯定看不出来对不对?
  我也看不出来,因为它根本就没有任何毛病,程序可以正常运行结束:
  接着,我把程序修改为这样,新增标号为 ③ 的这几行代码:
  这里调用的是线程池的 shutdown 方法,目的是想等线程池把任务处理完成后,让程序退出。
  来,你又说说这个程序跑起来有什么毛病?
  你肯定又没有看不来对不对?
  我也没有,因为它根本就没有任何毛病,程序可以正常运行结束:
  好,接下来,我又要开始变形了。
  程序变成这样:
  注意我这里用的是 shutdownNow 方法,意思就是我想立即关闭前面的那个线程池,然后让整个程序退出。
  那么这个程序有什么问题呢?
  它是真的有问题,肉眼真不好看出来,但是我们可以先看一下运行结果:
  结果还是很好观察的。
  没有输出 "invokeAll returned",程序也没有退出。
  那么问题就来了:你说这是不是 BUG ?
  咱先不管原因是啥,从现象上看,这妥妥的是 BUG 了吧?
  我都调用 shutdownNow 了,想的就是立马关闭线程池,然后让整个程序退出,结果任务确实是没有执行了,但是程序也并没有退出啊,和我们预期的不符。
  所以,大胆一点,这就是一个 BUG!
  再来一个关于 shutdownNow 和 shutdown 方法输出对比图,更直观:
  至于这两个方法之间有什么区别,我就不讲了,你要是不知道就去网上翻翻,背一下。
  反正现在 BUG 已经能稳定复现了。
  接下来就是找出根因了。 根因
  根因怎么找呢?
  你先想想这个问题:程序应该退出却没有退出,是不是说明还有线程正在运行,准确的说是还有非守护线程正在运行?
  对了嘛,想到这里就好办了嘛。
  看线程堆栈嘛。
  怎么看?
  照相机啊,朋友们。我们的老伙计了,之前的文章里面经常露面,就它:
  你就这么轻轻的一点,就能看到有个线程它不对劲:
  它在 WAITING 状态,而导致它进入这个状态的代码通过堆栈信息,一眼就能定位到,就是 invokeAll 方法的 244 行,也就是这一行代码:
  at java.util.concurrent.AbstractExecutorService.invokeAll(AbstractExecutorService.java:244)
  既然问题出在 invokeAll 这个方法里面,那就得理解这个方法在干啥了。
  源码也不复杂,主要关注我框起来的这部分:
  标号为 ① 的地方,是把传入进来的任务封装为一个 Future 对象,先放到一个 List 里面,然后调用 execute 方法,也就是扔到线程池里面去执行。
  这个操作特别像是直接调用线程池的 submit() 方法,我给你对比一下:
  标号为 ② 的地方,就是循环前面放 Future 的 List,如果 Future 没有执行完成,就调用 Future 的 get 方法,阻塞等待结果。
  从堆栈信息上看,线程就阻塞在 Future 的 get 方法这里,说明这个 Future 一直没有被执行。
  为什么没有被执行?
  好,我们回到测试代码的这个地方:
  10 个任务,往核心线程数是 2 的线程池里面扔。
  是不是有两个可以被线程池里面的线程执行,剩下的 8 个进入到队列里面?
  好,我问你:调用 shutdownNow 之后,工作线程是不是直接就给干没了?剩下的 8 个是不是没有资源去执行了?
  话说回来,哪怕只有 1 个任务没有被执行呢?invokeAll 方法里面的 future.get() 是不是也得阻塞?
  但是,朋友们,但是啊,就在 BUG 如此清晰的情况下,上面的这个案例居然被官方给推翻了。
  怎么回事呢?
  带你看一下官方大佬的回复。
  哦,对不起,不是大佬,是官方巨佬 Martin 和 Doug 的回复:
  Martin 说:老铁,我看了你的代码,感觉没毛病啊?你听我说,shutdownNow 方法返回了一个 List 列表,里面放的就是还没有被执行任务。所以你还得拿着 shutdownNow 的返回搞一些事情才行。
  Doug 说:Martin 说的对。额外说一句:
  that"s why they are returned。
  they 指的就是这个 list。也就是说老爷子写代码的时候是考虑到这个情况了的,所以把没有执行的任务都返给了调用者。
  好吧,shutdownNow 方法是有返回值的,我之前居然没有注意到这个细节:
  但是你仔细看这个返回值,是个 list 里面装的 Runnable,它不是 Future,我就不能调用 future.cancel() 方法。
  所以拿到这个返回值之后,我应该怎么取消任务呢?
  这个问题问得好啊。因为提问者也有这样的疑问:
  他在看到巨佬们说要对返回值做操作之后,一脸懵逼的回复说:哥老倌些,shutdownNow 方法返回的是一个List。至少对我来说,我不知道应该这么去取消这些任务。是不是应该在文档里面描述一下哦?
  Martin 老哥觉得这个返回确实有点迷惑性,他做了如下回复:
  线程池提交任务有两种方式。
  如果你用 execute() 方法提交 Runnable 任务,那么 shutdownNow 返回的是未被执行的 Runnable 的列表。
  如果你用 submit() 方法提交 Runnable 任务,那么会被封装为一个 FutureTask 对象,所以调用 shutdownNow 方法返回的是未被执行的 FutureTask 的列表:
  也就是说 shutdownNow 方法返回的 List 集合,里面装的既可能是 Runnable,也可能是 FutureTask,取决于你往线程池里面扔任务的时候调用的什么方法。
  FutureTask 是 Runnable 的子类:
  所以,基于 Martin 老哥的说法和他提供的代码,我们可以把测试用例修改为这样:
  遍历 shutdownNow 方法返回的 List 集合,然后判断是否 Future,如果是则强转为 Future,接着调用其 cancel 方法。
  这样,程序就能正常运行结束。
  这样看来,好像也确实不是一个 BUG,可以通过编码来避免它。 反转
  但是,朋友们,但是啊,前面都是我的铺垫,接下来剧情开始反转了。
  我们回到这个链接中:
  https://bugs.openjdk.org/browse/JDK-8286463
  这个链接里面提到了 DiscardPolicy 这个线程池拒绝策略。
  只要我稍微的把我们的 Demo 程序改变一点点,触发线程的 DiscardPolicy 拒绝策略,前面这个 bug 就真的是一个绕不过去的 bug 了。
  应该怎么改变呢?
  很简单,换个线程池就可以了:
  把我们之前这个核心线程数为 2,队列长度无限长的线程池替换为一个自定义线程池。
  这个自定义线程池的核心线程数、最大线程数、队列长度都是 1,采用的线程拒绝策略是 DiscardPolicy。
  其他的地方代码都不动,整个代码就变成了这样,我把代码贴出来给你看看,方便你直接运行: public class MainTest {      public static void main(String[] args) throws InterruptedException {          List> tasks = new ArrayList<>();         for (int i = 0; i < 10; i++) {             int finalI = i;             tasks.add(() -> {                 System.out.println("callable " + finalI);                 Thread.sleep(500);                 return null;             });         }         ExecutorService executor = new ThreadPoolExecutor(                 1,                 1,                 1,                 TimeUnit.SECONDS,                 new ArrayBlockingQueue<>(1),                 new ThreadPoolExecutor.DiscardPolicy()         ); //        ExecutorService executor = Executors.newFixedThreadPool(2);         Thread executorInvokerThread = new Thread(() -> {             try {                 executor.invokeAll(tasks);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println("invokeAll returned");         });         executorInvokerThread.start();          Thread.sleep(800);         System.out.println("shutdown");         List runnables = executor.shutdownNow();         for (Runnable r : runnables) {             if (r instanceof Future) ((Future<?>)r).cancel(false);         }         System.out.println("Shutdown complete");     } }
  然后我们先把程序运行起来看结果:
  诶,怎么回事?
  我明明处理了 shutdownNow 的返回值呢,怎么程序又没有输出 "invokeAll returned" 了,又阻塞在 invokeAll 方法上了?
  就算我们不知道为什么程序没有停下来,但是从表现上看,这玩意肯定是 bug 了吧?
  接下来我带你分析一下为什么会出现这个现象。
  首先我问你在我们的案例里面,这个线程池最多能容纳几个任务?
  是不是最多只能接收 2 个任务?
  最多只能接收 2 个任务,是不是说明我有 8 个任务是处理不了的,需要执行线程池的拒绝策略?
  但是我们的拒绝策略是什么?
  是 DiscardPolicy,它的实现是这样的,也就是静默处理,丢弃任务,也不抛出异常:
  好,到这里你又接着想,shutdownNow 返回的是什么东西,是不是线程池里面还没来得及执行的任务,也就是队列里面的任务?
  但是队列里面最多也就一个任务,返回回来给你取消了也没用。
  所以,这个案例和处不处理 shutdownNow 的返回值没有关系。
  关键的是被拒绝的这 8 个任务,或者说关键是触发了 DiscardPolicy 拒绝策略。
  触发一次和触发多次的效果都是一样的,在我们这个自定义线程池加 invokeAll 方法这个场景下,只要有任何一个任务被静默处理了,就算玩蛋。
  为什么这样说呢?
  我们先看看默认的线程池拒绝策略 AbortPolicy 的实现方式:
  被拒绝执行之后,它是会抛出异常,然后在 invokeAll 方法里面会被捕捉到,所以不会阻塞:
  如果是静默处理,你没有任何地方让这个被静默处理的 Future 抛出异常,也没用任何地方能调用它的 cancel 方法,所以这里就会一直阻塞。
  所以,这就是 BUG。
  那么针对这个 BUG,官方是怎么回复呢?
  Martin 巨佬回复说:我觉得吧,应该在文档上说明一下,DiscardPolicy 这个拒绝策略,在真实的场景中很少使用,不建议大家使用。要不,你把它当作一个 feature?
  我觉得言外之意就是:我知道这是一个 BUG 了,但是你非得用 DiscardPolicy 这个不会在实际编码中使用的拒绝策略来说事儿,我觉得你是故意来卡 BUG 的。
  我对于这个回复是不满意的。
  Martin 老哥是有所不知,我们面试的时候有一个八股文环节,其中的一个老八股题是这样的:
  你有没有自定义过线程池拒绝策略?
  如果有一些大聪明,在自定义线程池拒绝策略的时候,写出了一个花里胡哨的,但是又等效于 DiscardPolicy 的拒绝策略。
  也就是又没放进队列,又没抛出异常,不管你代码写的多花哨,一样的是有这个问题。
  所以,我觉得还是 invokeAll 方法的设计问题,一个不能在调用线程之外被其他线程访问的 Future 就不应该被设计出来。
  这违背了 Future 这个对象的设计理念。
  所以我才说这是 BUG,也是设计问题。
  什么,你问我应该怎么设计?
  对不起,无可奉告。

李宁新品!幻影3夏季版好帅,韦德001配色好评,韦德9还有新配色这篇内容狗哥和大家聊聊李宁的新品,东西很多,基本上都是篮球鞋,设计上非常到位,配色也很讲究,并且,似乎更加注重实战性能了,轻量化的设计很适合马上到来的夏日,国产一哥的产品力是真的强夏季穿裙子,选择这5条就足够了,简单随意又清爽,不信你瞧随着夏季的到来,衣橱里的衣服也来了个360度大转变,要说里面摆放最多的,那必定是裙子,裙子温柔大方,款式多样化,最受女性朋友的喜爱。今天的裙子就像美丽的花朵,百花齐放,争相夺艳,看今年最懂女性穿搭的是经济学家?已经在给夏天囤货了。犹记去年自己还是小辣妹,今年却买了一堆长裙。朋友很专业地向我讲解这叫短裙效应。短裙效应又叫裙边理论,由1926年美国经济学家乔治泰勒提出,简单解释就是当经济好时女性腰肌劳损应该注意哪些?腰肌劳损是当代生活中比较高发的一类疾病,而且它的发病群体比较广泛,不仅仅是中老年人,很多年轻人也会出现腰肌劳损的问题。而且女性因为工作因素的影响,也有可能受到腰肌劳损的困扰,患病期教爸妈用手机用好负一屏,出行快人一步五一旅游季即将来临,父母还在为不知道怎么购票而烦恼吗?还在为旅游不知道要住在何处而忧愁吗?所到之处不知道有何风景?有何美食?疫情期间出行要健康码,不知道在哪里打开?教爸妈用好智慧助vivo与联发科这次双芯合作,让高端手机市场再生变局如果要给这两年智能手机市场总结关键词,冲击高端一定位列其中。根据根据CounterpointResearch的数据,2021年,全球高端手机(平均批发价400美元以上)的销售额增速手机APP内存占用越来越大,光王者就占15G!该如何解决内存问题?不知道大家有没有玩过王者荣耀这款游戏?相信下载过这个游戏的玩家都遇到过一个问题,就是它实在太吃内存了,更新了几次后就占用了15G。对此,很多人在内存不足的时候都会陷入两难境地,要不Unihertz新款小屏全键盘手机发布,配置联发科处理器Unihertz是一个喜欢在设计上剑走偏锋的小众手机品牌,旗下有很多造型独特的智能手机产品。品牌最新发布的TitanSlim也是其中之一,它配置了Qwerty全键盘,并搭载联发科H徐峥罕见公开露面,面部浮肿精神萎靡引热议近日,随着张庭陷入传销风波,17亿元的房产也被查封。而与张庭个人及公司,有相关联系的多位明星,也被牵连其中。明道如今留台照顾母亲至今未归,徐峥及陶虹夫妇,也在近日被推至风口浪尖。作华为智选新机支持5G网络!Hinova9z能成新一代卷王吗?中邮通信旗下智能终端品牌Hinova于4月17日发布旗下第四款全新产品Hinova9系列的Hinova9z。Hinova品牌简单来说是华为智选品牌,即采用华为的ID设计和质量管控标为什么白头发越长越多?是因为老了?医生提醒或与这些原因有关导语随着年龄的不断增长,身体里的各项器官组织功能也出现了一个逐渐衰退的情况,慢慢就会进入到衰老的这个过程当中。在衰老的阶段可能有一些人会出现脱发头发变白等方面的问题,就会容易影响到
长在心上的月亮长在心上的月亮文杨杰清说起思乡。忽然眼泪成行银色的记忆,一片片飞出夜空月亮升起,思念下降月亮,是故乡的眼睛谁的牵挂碰碎一地月光停靠一个肩膀。承担泪水的重量。昨日却已悄然丢失在远方思孕期必看巧克力韭菜菠萝芒果这些食品要注意!孕期饮食是孕妇和胎儿健康的重要保证,合理的饮食习惯可以减少孕期并发症和胎儿畸形的风险。然而,许多准妈妈对于一些食品的安全性知道得不够充分。今天,我们就来为大家介绍孕期饮食的红黑榜,科技创新助力强国战略近日,中共中央国务院印发党和国家机构改革方案,决定组建中央科技委员会,重新组建科学技术部,中央科技委员会的办事机构职责由重组后的科学技术部整体承担。中央科技委员会的组建和科技部的重潮流AnderssonBell春夏新品法拉利软顶敞篷RomaSpider曝光法拉利正式发表敞篷版本超跑RomaSpider韩国品牌AnderssonBell发布2023春夏系列男女装新品。01hrKITH2023春季系列第二弹viaKITH日前,KITH2多厂商布局推新品渗透率提升空间大屏下摄像爆点临近?财联社3月19日讯(记者王碧微陆婷婷)屏下正成为大厂争抢的新方向。日前,努比亚屏下摄像新机Z50Ultra正式开售,推出首周获得京东天猫平台手机商品榜销量及销售额双冠。消费电子风向RetroidPocketFlip翻盖式复古掌机发布,尺寸和任天堂DS相当IT之家3月18日消息,RetroidPocket系列掌机制造商展示了一款采用翻盖设计的新型号RetroidPocketFlip。该设备大小与任天堂DS相当,不过底部没有屏幕。Re续航200公里,雅迪新日小刀发布3款长续航电动车,性能出色请您在阅读前,先点击上面的关注。感谢您的支持,我们将为您带来更多有价值的内容。在日常代步出行这一块,电动车起到了重要作用。从广大用户的需求来看,比较看重的要么就是续航要么就是速度要华为mateX3强势来袭,昆仑玻璃超轻薄机身,价格更惊喜好内容我来评三月份手机市场确实不是一般火爆,新机真正做到了一款接一款,但要说起在如此多手机中真正让大家期待的其实也就华为了。目前华为已经正式官宣,将在3月23日正式发布华为P60系身藏黑暗,清扫不洁这是一款90年代复古风格的潜行类玩法游戏,玩家在游戏中将扮演清道夫这一职业,在一次又一次的凶杀现场中,替黑帮处理被谋害的诸多尸体清理凶杀证据物件以及打扫凶案现场环境。简而言之,为黑非人哉花果山组建西游男团,烈烈沙僧八戒遭开除,哪吒杨戬来救场非人哉漫画又更新了,花果山的故事依旧继续,这次猴群们不再自创旅游景点了,可是盯上了舞台表演,准备借助齐天大圣的人气,打造一个西游男团。本来是好事,奈何闹出了不少趣事,下面就让我们一王者荣耀上分技巧王者荣耀上分技巧指南王者荣耀是一款备受玩家喜爱的竞技游戏,而在游戏中想要上分需要玩家掌握一些技巧和策略。下面就为大家详细介绍王者荣耀上分技巧。选择适合的英雄在游戏中选择适合自己的英