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

HashMap的死循环

  问题
  最近的几次面试中,我都问了是否了解HashMap在并发使用时可能发生死循环,导致cpu100%,结果让我很意外,都表示不知道有这样的问题,让我意外的是面试者的工作年限都不短。
  由于HashMap并非是线程安全的,所以在高并发的情况下必然会出现问题,这是一个普遍的问题,虽然网上分析的文章很多,还是觉得有必须写一篇文章,让关注我公众号的同学能够意识到这个问题,并了解这个死循环是如何产生的。
  如果是在单线程下使用HashMap,自然是没有问题的,如果后期由于代码优化,这段逻辑引入了多线程并发执行,在一个未知的时间点,会发现CPU占用100%,居高不下,通过查看堆栈,你会惊讶的发现,线程都Hang在hashMap的get()方法上,服务重启之后,问题消失,过段时间可能又复现了。
  这是为什么?原因分析
  在了解来龙去脉之前,我们先看看HashMap的数据结构。
  在内部,HashMap使用一个Entry数组保存key、value数据,当一对key、value被加入时,会通过一个hash算法得到数组的下标index,算法很简单,根据key的hash值,对数组的大小取模 hash & (length-1),并把结果插入数组该位置,如果该位置上已经有元素了,就说明存在hash冲突,这样会在index位置生成链表。
  如果存在hash冲突,最惨的情况,就是所有元素都定位到同一个位置,形成一个长长的链表,这样get一个值时,最坏情况需要遍历所有节点,性能变成了O(n),所以元素的hash值算法和HashMap的初始化大小很重要。
  当插入一个新的节点时,如果不存在相同的key,则会判断当前内部元素是否已经达到阈值(默认是数组大小的0.75),如果已经达到阈值,会对数组进行扩容,也会对链表中的元素进行rehash。实现
  HashMap的put方法实现:
  1、判断key是否已经存在public V put(K key, V value) {     if (key == null)         return putForNullKey(value);     int hash = hash(key);     int i = indexFor(hash, table.length);     // 如果key已经存在,则替换value,并返回旧值     for (Entry e = table[i]; e != null; e = e.next) {         Object k;         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {             V oldValue = e.value;             e.value = value;             e.recordAccess(this);             return oldValue;         }     }      modCount++;     // key不存在,则插入新的元素     addEntry(hash, key, value, i);     return null; }
  2、检查容量是否达到阈值thresholdvoid addEntry(int hash, K key, V value, int bucketIndex) {     if ((size >= threshold) && (null != table[bucketIndex])) {         resize(2 * table.length);         hash = (null != key) ? hash(key) : 0;         bucketIndex = indexFor(hash, table.length);     }      createEntry(hash, key, value, bucketIndex); }
  如果元素个数已经达到阈值,则扩容,并把原来的元素移动过去。
  3、扩容实现void resize(int newCapacity) {     Entry[] oldTable = table;     int oldCapacity = oldTable.length;     ...      Entry[] newTable = new Entry[newCapacity];     ...     transfer(newTable, rehash);     table = newTable;     threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
  这里会新建一个更大的数组,并通过transfer方法,移动元素。void transfer(Entry[] newTable, boolean rehash) {     int newCapacity = newTable.length;     for (Entry e : table) {         while(null != e) {             Entry next = e.next;             if (rehash) {                 e.hash = null == e.key ? 0 : hash(e.key);             }             int i = indexFor(e.hash, newCapacity);             e.next = newTable[i];             newTable[i] = e;             e = next;         }     } }
  移动的逻辑也很清晰,遍历原来table中每个位置的链表,并对每个元素进行重新hash,在新的newTable找到归宿,并插入。案例分析
  假设HashMap初始化大小为4,插入个3节点,不巧的是,这3个节点都hash到同一个位置,如果按照默认的负载因子的话,插入第3个节点就会扩容,为了验证效果,假设负载因子是1.void transfer(Entry[] newTable, boolean rehash) {     int newCapacity = newTable.length;     for (Entry e : table) {         while(null != e) {             Entry next = e.next;             if (rehash) {                 e.hash = null == e.key ? 0 : hash(e.key);             }             int i = indexFor(e.hash, newCapacity);             e.next = newTable[i];             newTable[i] = e;             e = next;         }     } }
  以上是节点移动的相关逻辑。
  插入第4个节点时,发生rehash,假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组。
  假设 线程2 在执行到Entry next = e.next;之后,cpu时间片用完了,这时变量e指向节点a,变量next指向节点b。
  线程1继续执行,很不巧,a、b、c节点rehash之后又是在同一个位置7,开始移动节点
  第一步,移动节点a
  第二步,移动节点b
  注意,这里的顺序是反过来的,继续移动节点c
  这个时候 线程1 的时间片用完,内部的table还没有设置成新的newTable, 线程2 开始执行,这时内部的引用关系如下:
  这时,在 线程2 中,变量e指向节点a,变量next指向节点b,开始执行循环体的剩余逻辑。Entry next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next;
  执行之后的引用关系如下图
  执行后,变量e指向节点b,因为e不是null,则继续执行循环体,执行后的引用关系
  变量e又重新指回节点a,只能继续执行循环体,这里仔细分析下:
  1、执行完Entry next = e.next;,目前节点a没有next,所以变量next指向null;
  2、e.next = newTable[i]; 其中 newTable[i] 指向节点b,那就是把a的next指向了节点b,这样a和b就相互引用了,形成了一个环;
  3、newTable[i] = e 把节点a放到了数组i位置;
  4、e = next; 把变量e赋值为null,因为第一步中变量next就是指向null;
  所以最终的引用关系是这样的:
  节点a和b互相引用,形成了一个环,当在数组该位置get寻找对应的key时,就发生了死循环。
  另外,如果线程2把newTable设置成到内部的table,节点c的数据就丢了,看来还有数据遗失的问题。总结
  所以在并发的情况,发生扩容时,可能会产生循环链表,在执行get的时候,会触发死循环,引起CPU的100%问题,所以一定要避免在并发环境下使用HashMap。
  曾经有人把这个问题报给了Sun,不过Sun不认为这是一个bug,因为在HashMap本来就不支持多线程使用,要并发就用ConcurrentHashmap
  私信666领取资料

沙丘中的双月凌空三星混绕,会真实发生吗?人类在地球之外的其他星球如何生活?会发生什么样的故事?这是很多科幻文艺作品的背景设定,无论太空歌剧星战,还是三体之类硬科幻。近期上映的科幻电影沙丘,故事即在沙漠行星厄拉科斯展开。但折叠屏OUT了!华为小米OV进军卷轴屏,国产机终于赢了三星?手机行业技术更新非常快,比如屏幕方面,各大厂商探索出了各种形态的屏幕,比如滑盖屏翻盖屏折叠屏现在,折叠屏已经落后了,卷轴屏才是风口!相比折叠屏目前依然无法完全消除的折痕,卷轴屏就完极限竞速地平线5将会在第二第三赛季添加24辆新车根据极限竞速地平线5游戏官方长达一小时的直播中了解到,游戏第2赛季将从12月9日开始,将持续四个星期夏季秋季冬季和春季各一个星期,在这之后游戏将会开启第3赛季。第2赛季新车包括的多12月份开始,医保迎来5个重要变化,事关个人利益,你需要看看12月,医保方面发生了一些重要的变化,大家需要及时了解。一是居民医保缴费进入倒计时,错过将给个人带来损失2022年的城乡居民医疗保险缴费已经开启了,大家需要看看当地的缴费时间,不要商汤科技今日开启公开招股,国际配售部分已获足额认购文观网财经王木巨12月7日,国内最大的人工智能独角兽营收额居AI四小龙之首的商汤科技开启公开招股,其国际配售部分(占全部发行数量的90)已经获得足额认购。据悉,商汤科技计划全球公开12月,社保和工资迎来关键期,注意三件事12月份,是一个重要的月份,社保和工资迎来了关键期,温馨提醒大家注意三件事,涉及到大家的切身利益,一定要引起重视,避免遭受不好的后果。集中缴费期截止,年底前完成社保缴费先来说社保迎人生苦短,余生很贵,别跟自己过不去开篇语人生如梦,梦如人生。一年四季流转,转瞬即逝,即便你攒下黄金万两,临走也带不走分毫,争名夺利几十载,过后不过一场云烟。人这一辈子啊,就是一个品尝百味,品味百态的过程。年幼时,我搏击名将方便终于承认黑哨输给杜姆贝却被判赢,是人生一大污点中国自由搏击名将死神方便,这位当年由武林风捧红的武者,至今保持着所谓职业生涯59战全胜的不败世界纪录,特别是当年,他曾战胜世界排名第一的马库斯,更是名噪一时,也因此为方便赢得了一世人生N条励志语录1,万丈红尘三杯酒,千秋大业一壶茶2,现实告诉你,不争气,连你最亲的人都会瞧不起你!不信你试试!3,生活告诉你,没什么都可以,千万不能没钱!4,你行的时候,怎么都行!你不行的时候,以为听懂了歌词,其实那是人生听歌成了很多人的一种习惯。上下班的路上,在家里的,耳朵挂着耳机的时间也越来越长了。很多时候,我们一开始听的歌没有明白歌词的含义,但是当我们经历了相类似的事情后,再听这首歌,会深刻的地球上的最大生命体无法遏制疯狂生长,蓝鲸都要排其后太阳系八大行星,地球是最特别的那一颗,从太空中回望地球,我们的家园是一颗蓝色星球。从地球上最简单的单细胞生命出现,发展到今天已经38亿年之久,虽然经过了数次的生物大灭绝事件,每次一
专为孩子学习,全屋定制的书桌咋如厕所一般?网友们不淡定了无意中刷到一条消息,说的是一家三个姐弟重视孩子教育,一起齐心协力为家里所有孩子提供良好学习环境的故事。弟弟妹妹在外做生意很忙,姐姐则在家照顾彼此的孩子,负责孩子的学习。为此,弟弟给火车上的厕所为什么是通的?粪便直接排到轨道上,不脏吗?头条创作挑战赛趁着现在还年轻,背起行囊,带着钱,来一场旅行吧。很多人旅游,选择的交通工具是火车高铁,当然了不是说选择飞机的游客没有,只是很少而已。在高铁还没普及之前,很多地方只有绿半夜怕黑,不敢上厕所的不止你一个人这是大多人都会害怕会经历的一件事害怕夜晚,和童年的害怕不同,这是很多人一直不能摆脱的恐惧。一些恐惧,是由一些特殊的生活经历或天生所形成的。但对于黑暗的恐惧,它是以黑暗恐怖这种极端的免费无广告无水印!这几款扫描APP,让你的办公更高效因为疫情的原因,现在不少城市动不动就静默,这严重影响了大家的工作与学习,而居家办公与上网课也让打印文件扫描等成了必不可少的需求,大部分家庭并没有打印机,虽然打印的问题只能通过打印机双十一手机价格战即将开打,卢伟冰直接放大招,Redmi影像越级?双十一手机价格战即将开打,卢伟冰直接放大招,Redmi影像越级?这不?双十一手机价格战即将开打嘛,各家手机厂商纷纷推出走量中端机型,势必要在这次竞争中拔得头筹。其中,近期即将亮相的最新双11的各款iPhonePro与iPhoneProMax价格走势由于一直在考虑双11期间入手iPhoneProMax或iPhonePro,我对近几年的iPhonePro和iPhoneProMax做了一个详细的价格分析预测,以及各个机型的详细对比升降桌内嵌摄像头偷拍隐私?董事长对隐私没兴趣,是为了测体温近日,乐歌升降桌安装有隐藏摄像头一事引起关注和热议。10月25日乐歌做出了回应,消费者投诉是因为退货运费问题才投诉的,现在已经解决了,产品退回后检测没有发现问题,但对产品做了下架处显卡矿难,显卡不降显示器降?5XX的2K144HZ显示器头条创作挑战赛双十一显示器的狂欢,上个月小编之前的带鱼屏主板坏了,于是就一直在关注显示器的价格,一开始的预算是1000块钱买一块27寸2K144HZ的高色域显示器,因为去年帮朋友买你被新型不孝戳中了吗?又真实又扎心最近突然火了一个词,叫新型不孝。指的是有的父母以为孩子在外面工作是挖金子,自己紧衣缩食辛苦大半辈子,终于等到孩子走进社会,认为完成任务了,就等着孩子衣锦还乡,扬眉吐气。但是他们不知2亿像素210W秒充256G,只需2399元?国产手机玩太大了看了RedmiNote12系列发布会后,笔者只有一个感受再见了,iPhone14!说实话,之前一直在纠结入手iPhone13或iPhone14,但看了RedmiNote12系列发布登场26分钟,得0分!5个赛季毫无进步!这么下去,要被辽篮放弃了因为本赛季吹罚尺度的改变,在场上能冲的愿意拼的年轻球员能给球队带来很多帮助。我们球队也想提拔一些年轻队员,但是希望他们能够珍惜机会,如果是第四节这种表现,未来在球队很难立足。在球队9190!李楠救赎,1分爆冷击败辽宁,6胜2负联赛第一,咸鱼翻身了说起李楠,很多人还是对19年世界杯的那一次失误耿耿于怀,在从国家队主教练辞职之后,李楠在上赛季也是接过了江苏队主教练的教鞭,不过他执教的首个赛季,战绩并不是很好,在38轮比赛中,只皇马23兵败莱比锡,最高兴的或许是安切洛蒂欧冠小组赛F组第5轮,银河战舰作客红牛竞技场挑战RB莱比锡,23不敌败北,遭遇赛季首败。比赛过程中与结束后,很多球迷都对安切洛蒂的用人与布阵十分不解,对意大利老帅的整活儿进行了猛烈被央视多次报道的浙江小城,年均温24,九山半水半分田头条创作挑战赛这几年疫情反复,很多城市不是在封城中就是在封城的过程中,当地人也很少出去游玩了,只能在内部逛逛。但如果你在浙江,没有疫情下,可以去这座小城看看,其被央视多次报道过,就投资电动汽车行业的好时机?在经历了一个夏天的地缘政治和经济动荡推高油价扰乱本已紧张的供应链之后,电动汽车行业正在不稳定的基础上实现平衡。在2021年底和2022年初的井喷季之后,该行业已显著降温。尽管经济增北京发布21条文旅骑行线路730公里慢游京城中新网北京10月27日电(徐婧)觉醒年代青春骑行最美中轴品味古今畅游京西骑乐无穷26日,北京市文化和旅游局联合各区政府北京经济技术开发区管委会向市民游客推出21条漫步北京文旅骑行主31!女乒世界冠军逆转队友,率先晋级八强静候孙颖莎北京时间2022年10月27日,世界乒联世界杯决赛拉开战幕,揭幕战是一场日本德比战,由石川佳纯对阵木原美悠。经过一番激烈争夺,石川佳纯在先丢一局的情况下完成逆转,31淘汰木原美悠,王者复苏!上海230完爆深圳王哲林113狂虐大鸟李春江大石落地3415,上海队在和深圳队的首节比赛当中就以19分的优势保持领先。本场比赛之前上海队一胜六负,排名联盟第19位,因此对于李春江来说也是面临着极大的压力。尤其过去五场比赛全部失利,因国乒今日赛程马龙孙颖莎首秀,陈梦谨防冷门,15位高手一轮游10月27日,新乡WTT世界杯决赛展开首日较量。除了男乒世界第一樊振东在首轮轮空外,马龙孙颖莎领衔的国乒8将于今日迎来世界杯首秀,15场精彩比赛打响,这也意味着有15位高手会止步首轰47分!带领鱼腩击败辽宁队,创2项纪录,他不愧是CBA第一外援今天第八轮比赛,爆出本赛季以来最大冷门,昔日超级鱼腩江苏队击败了不可一世的辽宁,从而送给辽宁队赛季第二败,同时他们豪取四连胜,并冲进积分榜前二,把卫冕冠军辽宁队甩到身后,这是一个了11场一级赛冠军雌马雍容尔雅正式退役,将在欧洲配种近日,20202021赛季的澳大利亚年度最佳赛马胜出11场一级赛冠军的明星雌马雍容尔雅(VerryElleegant)正式退役!其马主团发布消息称,这匹优秀的雌马将留在北半球开启配鲁能VS上港首发浮现功勋飞翼复出,3王牌坐镇中场,克雷桑冲锋北京时间10月30日晚1930,中超联赛第20轮将上演一场巅峰对决,卫冕冠军山东鲁能泰山坐镇主场迎战中超豪门上海海港。前面21轮战罢,山东鲁能泰山16胜2平3负,积50分,暂时排在