1.HashMapå®ç°åç
2.震惊!ConcurrentHashMap里面也有死循环,作者留下的“彩蛋”了解一下?
3.HashMap为ä»ä¹ä¸å®å
¨ï¼
4.多线程环境下,hashmap为什么会出现死循环?
5.hashmap1.7和1.8的区别
HashMapå®ç°åç
HashMapå¨å®é å¼åä¸ç¨å°çé¢çé常é«ï¼é¢è¯ä¸ä¹æ¯çç¹ãæ以å³å®åä¸ç¯æç« è¿è¡åæï¼å¸æ对æ³çæºç ç人起å°ä¸äºå¸®å©ï¼çä¹åéè¦å¯¹é¾è¡¨æ¯è¾çæã以ä¸é½æ¯æèªå·±çç解ï¼æ¬¢è¿è®¨è®ºï¼åçä¸å¥½è½»å·ã
HashMapä¸çæ°æ®ç»æ为æ£å表ï¼åååå¸è¡¨ãå¨è¿éæä¼å¯¹æ£å表è¿è¡ä¸ä¸ªç®åçä»ç»ï¼å¨æ¤ä¹åæ们éè¦å å顾ä¸ä¸ æ°ç»ãé¾è¡¨çä¼ç¼ºç¹ã
æ°ç»åé¾è¡¨çä¼ç¼ºç¹åå³äºä»ä»¬åèªå¨å åä¸åå¨ç模å¼ï¼ä¹å°±æ¯ç´æ¥ä½¿ç¨é¡ºåºåå¨æé¾å¼åå¨å¯¼è´çãæ 论æ¯æ°ç»è¿æ¯é¾è¡¨ï¼é½æææ¾ç缺ç¹ãèå¨å®é ä¸å¡ä¸ï¼æ们æ³è¦çå¾å¾æ¯å¯»åãå é¤ãæå ¥æ§è½é½å¾å¥½çæ°æ®ç»æï¼æ£å表就æ¯è¿æ ·ä¸ç§ç»æï¼å®å·§å¦çç»åäºæ°ç»ä¸é¾è¡¨çä¼ç¹ï¼å¹¶å°å ¶ç¼ºç¹å¼±åï¼å¹¶ä¸æ¯å®å ¨æ¶é¤ï¼
æ£å表çåæ³æ¯å°keyæ å°å°æ°ç»çæ个ä¸æ ï¼ååçæ¶åéè¿keyè·åå°ä¸æ ï¼indexï¼ç¶åéè¿ä¸æ ç´æ¥ååãé度æå¿«ï¼èå°keyæ å°å°ä¸æ éè¦ä½¿ç¨æ£åå½æ°ï¼åååå¸å½æ°ã说å°åå¸å½æ°å¯è½æ人已ç»æ³å°äºï¼å¦ä½å°keyæ å°å°æ°ç»çä¸æ ã
å¾ä¸è®¡ç®ä¸æ 使ç¨å°äºä»¥ä¸ä¸¤ä¸ªå½æ°ï¼
å¼å¾æ³¨æçæ¯ï¼ä¸æ 并ä¸æ¯éè¿hashå½æ°ç´æ¥å¾å°çï¼è®¡ç®ä¸æ è¿è¦å¯¹hashå¼åindex()å¤çã
Psï¼å¨æ£å表ä¸ï¼æ°ç»çæ ¼åå«å桶ï¼ä¸æ å«å桶å·ï¼æ¡¶å¯ä»¥å å«ä¸ä¸ªkey-value对ï¼ä¸ºäºæ¹ä¾¿ç解ï¼åæä¸ä¼ä½¿ç¨è¿ä¸¤ä¸ªåè¯ã
以ä¸æ¯åå¸ç¢°æç¸å ³ç说æï¼
以ä¸æ¯ä¸æ å²çªç¸å ³ç说æï¼
å¾å¤äººè®¤ä¸ºåå¸å¼ç碰æåä¸æ å²çªæ¯åä¸ä¸ªä¸è¥¿ï¼å ¶å®ä¸æ¯çï¼å®ä»¬çæ£ç¡®å ³ç³»æ¯è¿æ ·çï¼hashCodeåç碰æï¼åä¸æ ä¸å®å²çªï¼èä¸æ å²çªï¼hashCode并ä¸ä¸å®ç¢°æ
ä¸ææå°ï¼å¨jdk1.8以åHashMapçå®ç°æ¯æ£å表 = æ°ç» + é¾è¡¨ï¼ä½æ¯å°ç®å为æ¢æ们è¿æ²¡æçå°é¾è¡¨èµ·å°çä½ç¨ãäºå®ä¸ï¼HashMapå¼å ¥é¾è¡¨çç¨æå°±æ¯è§£å³ä¸æ å²çªã
ä¸å¾æ¯å¼å ¥é¾è¡¨åçæ£å表ï¼
å¦ä¸å¾æ示ï¼å·¦è¾¹çç«æ¡ï¼æ¯ä¸ä¸ªå¤§å°ä¸ºçæ°ç»ï¼å ¶ä¸åå¨çæ¯é¾è¡¨ç头ç»ç¹ï¼æ们ç¥éï¼æ¥æé¾è¡¨ç头ç»ç¹å³å¯è®¿é®æ´ä¸ªé¾è¡¨ï¼æ以认为è¿ä¸ªæ°ç»ä¸çæ¯ä¸ªä¸æ é½åå¨çä¸ä¸ªé¾è¡¨ãå ¶å ·ä½åæ³æ¯ï¼å¦æåç°ä¸æ å²çªï¼ååæå ¥çèç¹ä»¥é¾è¡¨çå½¢å¼è¿½å å°åä¸ä¸ªèç¹çåé¢ã
è¿ç§ä½¿ç¨é¾è¡¨è§£å³å²çªçæ¹æ³å«åï¼æé¾æ³ï¼åå«é¾å°åæ³ï¼ãHashMap使ç¨çå°±æ¯æé¾æ³ï¼æé¾æ³æ¯å²çªåç以åç解å³æ¹æ¡ã
Qï¼æäºæé¾æ³ï¼å°±ä¸ç¨æ å¿åçå²çªåï¼
Aï¼å¹¶ä¸æ¯ï¼ç±äºå²çªçèç¹ä¼ä¸åçå¨é¾è¡¨ä¸è¿½å ï¼å¤§éçå²çªä¼å¯¼è´å个é¾è¡¨è¿é¿ï¼ä½¿æ¥è¯¢æ§è½éä½ãæ以ä¸ä¸ªå¥½çæ£å表çå®ç°åºè¯¥ä»æºå¤´ä¸åå°å²çªåççå¯è½æ§ï¼å²çªåççæ¦çååå¸å½æ°è¿åå¼çååç¨åº¦æç´æ¥å ³ç³»ï¼å¾å°çåå¸å¼è¶ååï¼å²çªåççå¯è½æ§è¶å°ã为äºä½¿åå¸å¼æ´ååï¼HashMapå é¨åç¬å®ç°äºhash()æ¹æ³ã
以ä¸æ¯æ£å表çåå¨ç»æï¼ä½æ¯å¨è¢«è¿ç¨å°HashMapä¸æ¶è¿æå ¶ä»éè¦æ³¨æçå°æ¹ï¼è¿éä¼è¯¦ç»è¯´æã
ç°å¨æä»¬æ¸ æ¥äºæ£å表çåå¨ç»æï¼ç»å¿ç人åºè¯¥å·²ç»åç°äºä¸ä¸ªé®é¢ï¼Javaä¸æ°ç»çé¿åº¦æ¯åºå®çï¼æ 论åå¸å½æ°æ¯å¦ååï¼éçæå ¥å°æ£å表ä¸æ°æ®çå¢å¤ï¼å¨æ°ç»é¿åº¦ä¸åçæ åµä¸ï¼é¾è¡¨çé¿åº¦ä¼ä¸æå¢å ãè¿ä¼å¯¼è´é¾è¡¨æ¥è¯¢æ§è½ä¸ä½³ç缺ç¹åºç°å¨æ£å表ä¸ï¼ä»è使æ£å表失å»åæ¬çæä¹ã为äºè§£å³è¿ä¸ªé®é¢ï¼HashMapå¼å ¥äºæ©å®¹ä¸è´è½½å åã
以ä¸æ¯åæ©å®¹ç¸å ³çä¸äºæ¦å¿µå解éï¼
Psï¼æ©å®¹è¦éæ°è®¡ç®ä¸æ ï¼æ©å®¹è¦éæ°è®¡ç®ä¸æ ï¼æ©å®¹è¦éæ°è®¡ç®ä¸æ ï¼å 为ä¸æ ç计ç®åæ°ç»é¿åº¦æå ³ï¼é¿åº¦æ¹åï¼ä¸æ ä¹åºå½éæ°è®¡ç®ã
å¨1.8åå ¶ä»¥ä¸çjdkçæ¬ä¸ï¼HashMapåå¼å ¥äºçº¢é»æ ã
红é»æ çå¼å ¥è¢«ç¨äºæ¿æ¢é¾è¡¨ï¼ä¸æ说å°ï¼å¦æå²çªè¿å¤ï¼ä¼å¯¼è´é¾è¡¨è¿é¿ï¼éä½æ¥è¯¢æ§è½ï¼ååçhashå½æ°è½ææçç¼è§£å²çªè¿å¤ï¼ä½æ¯å¹¶ä¸è½å®å ¨é¿å ãæ以HashMapå å ¥äºå¦ä¸ç§è§£å³æ¹æ¡ï¼å¨å¾é¾è¡¨å追å èç¹æ¶ï¼å¦æåç°é¾è¡¨é¿åº¦è¾¾å°8ï¼å°±ä¼å°é¾è¡¨è½¬ä¸ºçº¢é»æ ï¼ä»¥æ¤æåæ¥è¯¢çæ§è½ã
震惊!ConcurrentHashMap里面也有死循环,作者留下的“彩蛋”了解一下?
在探讨一个近期发现的JDK 8的BUG时,我们了解到Dubbo 2.7.7版本更新点的超强atr指标源码描述中,涉及到了与JDK相关的修复。这个BUG实际上是位于闻名的concurrent包中的computeIfAbsent方法。在JDK 9中被修复,修复者正是Doug Lea。由于ConcurrentHashMap就是Doug Lea的杰作,这个BUG可以被视作“谁污染谁治理”。为了理解这个BUG的成因,需要先了解computeIfAbsent方法的用途。
computeIfAbsent方法的目的是当Map中某个key对应的值不存在时,通过调用mappingFunction函数并返回该函数执行结果(非null)作为key的flyme7源码值。如初始化一个ConcurrentHashMap,第一次获取名为why的value,若未获取到,则返回null。接着调用computeIfAbsent方法获取null后,调用getValue方法,将返回值与当前key关联起来。因此,第二次获取时能拿到"why技术"。
了解了方法的用途,接下来揭示这个BUG。通过链接,我们看到具体的描述指向了concurrent包中的computeIfAbsent方法。这个BUG在JDK 9中被修复,修复人正是java反射的源码Doug Lea。要理解BUG,需要先了解这个方法的工作流程。在JDK 8之后,computeIfAbsent方法提供了第二个参数mappingFunction,该方法的含义是当前Map中key对应的值不存在时,调用mappingFunction函数,并将该函数的执行结果(非null)作为该key的value返回。
通过一系列代码演示,我们发现正常情况下,Map应该显示{ AaAa=,BBBB=},但在JDK 8环境中运行给定的测试用例时,方法不会结束,而是陷入了死循环。这就是在线测试源码phpBUG。
在这个BUG被发现的过程中,提问的艺术也发挥了重要作用。Doug Lea和Pardeep在讨论中展示了提问和回答的策略。Pardeep提供的测试案例是转折点,它清晰地指出了问题所在。Doug Lea在问题提出后不久给出了回复,提出了解决问题的可能性,并且在后续的讨论中逐步明确了这个BUG的存在以及可能的解决方法。最终,这个BUG在JDK 9中得到了修复。
这个BUG的成因在于computeIfAbsent方法内部的另一个computeIfAbsent调用,导致了两个方法在处理相同的key时进入了死循环。在处理此BUG时,我们需要深入理解computeIfAbsent方法的工作流程。从代码分析中可以看出,anaconda人脸识别源码当key为"AaAa"和"BBBB"时,它们在进行计算和存储操作时,由于key的哈希值相同,导致了循环条件无法满足break的情况,从而进入了死循环。
总结这个BUG,我们发现当key的哈希值相同时,多次调用computeIfAbsent方法会导致死循环。Doug Lea在JDK 9中通过增加判断条件,避免了这种循环情况,从而修复了这个BUG。对于使用JDK 8的开发者,可以通过将computeIfAbsent方法的使用方式调整为先调用get方法,再使用putIfAbsent方法,以此避免遇到这个BUG。此外,我们还提到了线程安全问题,即虽然ConcurrentHashMap本身是线程安全的,但在使用时仍需注意避免线程冲突,以确保程序的正确性。
HashMap为ä»ä¹ä¸å®å ¨ï¼
åå ï¼JDK1.7 ä¸ï¼ç±äºå¤çº¿ç¨å¯¹HashMapè¿è¡æ©å®¹ï¼è°ç¨äºHashMap#transfer()ï¼å ·ä½åå ï¼æ个线ç¨æ§è¡è¿ç¨ä¸ï¼è¢«æèµ·ï¼å ¶ä»çº¿ç¨å·²ç»å®ææ°æ®è¿ç§»ï¼çCPUèµæºéæ¾å被æèµ·ç线ç¨éæ°æ§è¡ä¹åçé»è¾ï¼æ°æ®å·²ç»è¢«æ¹åï¼é ææ»å¾ªç¯ãæ°æ®ä¸¢å¤±ã
JDK1.8 ä¸ï¼ç±äºå¤çº¿ç¨å¯¹HashMapè¿è¡putæä½ï¼è°ç¨äºHashMap#putVal()ï¼å ·ä½åå ï¼å设两个线ç¨AãBé½å¨è¿è¡putæä½ï¼å¹¶ä¸hashå½æ°è®¡ç®åºçæå ¥ä¸æ æ¯ç¸åçï¼å½çº¿ç¨Aæ§è¡å®ç¬¬å è¡ä»£ç åç±äºæ¶é´çè尽导è´è¢«æèµ·ï¼è线ç¨Bå¾å°æ¶é´çåå¨è¯¥ä¸æ å¤æå ¥äºå ç´ ï¼å®æäºæ£å¸¸çæå ¥ï¼ç¶å线ç¨Aè·å¾æ¶é´çï¼ç±äºä¹åå·²ç»è¿è¡äºhash碰æçå¤æï¼æææ¤æ¶ä¸ä¼åè¿è¡å¤æï¼èæ¯ç´æ¥è¿è¡æå ¥ï¼è¿å°±å¯¼è´äºçº¿ç¨Bæå ¥çæ°æ®è¢«çº¿ç¨Aè¦çäºï¼ä»è线ç¨ä¸å®å ¨ã
æ¹åï¼
æ°æ®ä¸¢å¤±ãæ»å¾ªç¯å·²ç»å¨å¨JDK1.8ä¸å·²ç»å¾å°äºå¾å¥½ç解å³ï¼å¦æä½ å»é 读1.8çæºç ä¼åç°æ¾ä¸å°HashMap#transfer()ï¼å 为JDK1.8ç´æ¥å¨HashMap#resize()ä¸å®æäºæ°æ®è¿ç§»ã
2ãHashMap线ç¨ä¸å®å ¨çä½ç°ï¼
JDK1.7 HashMap线ç¨ä¸å®å ¨ä½ç°å¨ï¼æ»å¾ªç¯ãæ°æ®ä¸¢å¤±
JDK1.8 HashMap线ç¨ä¸å®å ¨ä½ç°å¨ï¼æ°æ®è¦ç
äºãHashMap线ç¨ä¸å®å ¨ãæ»å¾ªç¯ãæ°æ®ä¸¢å¤±ãæ°æ®è¦ççåå
1ãJDK1.7 æ©å®¹å¼åç线ç¨ä¸å®å ¨
HashMapç线ç¨ä¸å®å ¨ä¸»è¦æ¯åçå¨æ©å®¹å½æ°ä¸ï¼å ¶ä¸è°ç¨äºJDK1.7 HshMap#transfer()ï¼
void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> 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;
}
}
}
å¤å¶ä»£ç
è¿æ®µä»£ç æ¯HashMapçæ©å®¹æä½ï¼éæ°å®ä½æ¯ä¸ªæ¡¶çä¸æ ï¼å¹¶éç¨å¤´ææ³å°å ç´ è¿ç§»å°æ°æ°ç»ä¸ã头ææ³ä¼å°é¾è¡¨ç顺åºç¿»è½¬ï¼è¿ä¹æ¯å½¢ææ»å¾ªç¯çå ³é®ç¹ãç解äºå¤´ææ³åå继ç»å¾ä¸çæ¯å¦ä½é ææ»å¾ªç¯ä»¥åæ°æ®ä¸¢å¤±çã2ãæ©å®¹é ææ»å¾ªç¯åæ°æ®ä¸¢å¤±
å设ç°å¨æ两个线ç¨AãBåæ¶å¯¹ä¸é¢è¿ä¸ªHashMapè¿è¡æ©å®¹æä½ï¼
æ£å¸¸æ©å®¹åçç»ææ¯ä¸é¢è¿æ ·çï¼
ä½æ¯å½çº¿ç¨Aæ§è¡å°ä¸é¢transferå½æ°ç第è¡ä»£ç æ¶ï¼CPUæ¶é´çèå°½ï¼çº¿ç¨A被æèµ·ãå³å¦ä¸å¾ä¸ä½ç½®æ示ï¼
æ¤æ¶çº¿ç¨Aä¸ï¼e=3ãnext=7ãe.next=null
å½çº¿ç¨Açæ¶é´çèå°½åï¼CPUå¼å§æ§è¡çº¿ç¨Bï¼å¹¶å¨çº¿ç¨Bä¸æåçå®æäºæ°æ®è¿ç§»
éç¹æ¥äºï¼æ ¹æ®Javaå å模å¼å¯ç¥ï¼çº¿ç¨Bæ§è¡å®æ°æ®è¿ç§»åï¼æ¤æ¶ä¸»å åä¸newTableåtableé½æ¯ææ°çï¼ä¹å°±æ¯è¯´ï¼7.next=3ã3.next=nullã
éå线ç¨Aè·å¾CPUæ¶é´ç继ç»æ§è¡newTable[i] = eï¼å°3æ¾å ¥æ°æ°ç»å¯¹åºçä½ç½®ï¼æ§è¡å®æ¤è½®å¾ªç¯å线ç¨Açæ åµå¦ä¸ï¼
æ¥ç继ç»æ§è¡ä¸ä¸è½®å¾ªç¯ï¼æ¤æ¶e=7ï¼ä»ä¸»å åä¸è¯»åe.nextæ¶åç°ä¸»å åä¸7.next=3ï¼æ¤æ¶next=3ï¼å¹¶å°7éç¨å¤´ææ³çæ¹å¼æ¾å ¥æ°æ°ç»ä¸ï¼å¹¶ç»§ç»æ§è¡å®æ¤è½®å¾ªç¯ï¼ç»æå¦ä¸ï¼
æ¤æ¶æ²¡ä»»ä½é®é¢ã
ä¸è½®next=3ï¼e=3ï¼æ§è¡ä¸ä¸æ¬¡å¾ªç¯å¯ä»¥åç°ï¼3.next=nullï¼æ以æ¤è½®å¾ªç¯å°ä¼æ¯æåä¸è½®å¾ªç¯ã
æ¥ä¸æ¥å½æ§è¡å®e.next=newTable[i]å³3.next=7åï¼3å7ä¹é´å°±ç¸äºè¿æ¥äºï¼å½æ§è¡å®newTable[i]=eåï¼3被头ææ³éæ°æå ¥å°é¾è¡¨ä¸ï¼æ§è¡ç»æå¦ä¸å¾æ示ï¼
ä¸é¢è¯´äºæ¤æ¶e.next=nullå³next=nullï¼å½æ§è¡å®e=nullåï¼å°ä¸ä¼è¿è¡ä¸ä¸è½®å¾ªç¯ãå°æ¤çº¿ç¨AãBçæ©å®¹æä½å®æï¼å¾ææ¾å½çº¿ç¨Aæ§è¡å®åï¼HashMapä¸åºç°äºç¯å½¢ç»æï¼å½å¨ä»¥å对该HashMapè¿è¡æä½æ¶ä¼åºç°æ»å¾ªç¯ã
并ä¸ä»ä¸å¾å¯ä»¥åç°ï¼å ç´ 5å¨æ©å®¹æé´è¢«è«åç丢失äºï¼è¿å°±åçäºæ°æ®ä¸¢å¤±çé®é¢ã
3ãJDK1.8ä¸ç线ç¨ä¸å®å ¨
ä¸é¢çæ©å®¹é æçæ°æ®ä¸¢å¤±ãæ»å¾ªç¯å·²ç»å¨å¨JDK1.8ä¸å·²ç»å¾å°äºå¾å¥½ç解å³ï¼å¦æä½ å»é 读1.8çæºç ä¼åç°æ¾ä¸å°HashMap#transfer()ï¼å 为JDK1.8ç´æ¥å¨HashMap#resize()ä¸å®æäºæ°æ®è¿ç§»ã
为ä»ä¹è¯´ JDK1.8ä¼åºç°æ°æ®è¦ççæ åµï¼ æ们æ¥çä¸ä¸ä¸é¢è¿æ®µJDK1.8ä¸çputæä½ä»£ç ï¼
å ¶ä¸ç¬¬å è¡ä»£ç æ¯å¤ææ¯å¦åºç°hash碰æï¼å设两个线ç¨AãBé½å¨è¿è¡putæä½ï¼å¹¶ä¸hashå½æ°è®¡ç®åºçæå ¥ä¸æ æ¯ç¸åçï¼å½çº¿ç¨Aæ§è¡å®ç¬¬å è¡ä»£ç åç±äºæ¶é´çè尽导è´è¢«æèµ·ï¼è线ç¨Bå¾å°æ¶é´çåå¨è¯¥ä¸æ å¤æå ¥äºå ç´ ï¼å®æäºæ£å¸¸çæå ¥ï¼ç¶å线ç¨Aè·å¾æ¶é´çï¼ç±äºä¹åå·²ç»è¿è¡äºhash碰æçå¤æï¼æææ¤æ¶ä¸ä¼åè¿è¡å¤æï¼èæ¯ç´æ¥è¿è¡æå ¥ï¼è¿å°±å¯¼è´äºçº¿ç¨Bæå ¥çæ°æ®è¢«çº¿ç¨Aè¦çäºï¼ä»è线ç¨ä¸å®å ¨ã
é¤æ¤ä¹åï¼è¿æå°±æ¯ä»£ç ç第è¡å¤æ个++sizeï¼æ们è¿æ ·æ³ï¼è¿æ¯çº¿ç¨AãBï¼è¿ä¸¤ä¸ªçº¿ç¨åæ¶è¿è¡putæä½æ¶ï¼å设å½åHashMapçzise大å°ä¸ºï¼å½çº¿ç¨Aæ§è¡å°ç¬¬è¡ä»£ç æ¶ï¼ä»ä¸»å åä¸è·å¾sizeçå¼ä¸ºååå¤è¿è¡+1æä½ï¼ä½æ¯ç±äºæ¶é´çèå°½åªå¥½è®©åºCPUï¼çº¿ç¨Bå¿«ä¹çæ¿å°CPUè¿æ¯ä»ä¸»å åä¸æ¿å°sizeçå¼è¿è¡+1æä½ï¼å®æäºputæä½å¹¶å°size=åå主å åï¼ç¶å线ç¨Aå次æ¿å°CPU并继ç»æ§è¡(æ¤æ¶sizeçå¼ä»ä¸º)ï¼å½æ§è¡å®putæä½åï¼è¿æ¯å°size=ååå åï¼æ¤æ¶ï¼çº¿ç¨AãBé½æ§è¡äºä¸æ¬¡putæä½ï¼ä½æ¯sizeçå¼åªå¢å äº1ï¼ææ说è¿æ¯ç±äºæ°æ®è¦çå导è´äºçº¿ç¨ä¸å®å ¨ã
ä¸ãå¦ä½ä½¿HashMapå¨å¤çº¿ç¨æ åµä¸è¿è¡çº¿ç¨å®å ¨æä½ï¼
ä½¿ç¨ Collections.synchronizedMap(map)ï¼å è£ æåæ¥Mapï¼åçå°±æ¯å¨HashMapçæææ¹æ³ä¸synchronizedã
ä¾å¦ï¼Collections.SynchronizedMap#get()
public V get(Object key) {synchronized (mutex) {
return m.get(key);
}
}
å¤å¶ä»£ç
åãæ»ç»1ãHashMap线ç¨ä¸å®å ¨åå ï¼
åå ï¼
JDK1.7 ä¸ï¼ç±äºå¤çº¿ç¨å¯¹HashMapè¿è¡æ©å®¹ï¼è°ç¨äºHashMap#transfer()ï¼å ·ä½åå ï¼æ个线ç¨æ§è¡è¿ç¨ä¸ï¼è¢«æèµ·ï¼å ¶ä»çº¿ç¨å·²ç»å®ææ°æ®è¿ç§»ï¼çCPUèµæºéæ¾å被æèµ·ç线ç¨éæ°æ§è¡ä¹åçé»è¾ï¼æ°æ®å·²ç»è¢«æ¹åï¼é ææ»å¾ªç¯ãæ°æ®ä¸¢å¤±ã
JDK1.8 ä¸ï¼ç±äºå¤çº¿ç¨å¯¹HashMapè¿è¡putæä½ï¼è°ç¨äºHashMap#putVal()ï¼å ·ä½åå ï¼å设两个线ç¨AãBé½å¨è¿è¡putæä½ï¼å¹¶ä¸hashå½æ°è®¡ç®åºçæå ¥ä¸æ æ¯ç¸åçï¼å½çº¿ç¨Aæ§è¡å®ç¬¬å è¡ä»£ç åç±äºæ¶é´çè尽导è´è¢«æèµ·ï¼è线ç¨Bå¾å°æ¶é´çåå¨è¯¥ä¸æ å¤æå ¥äºå ç´ ï¼å®æäºæ£å¸¸çæå ¥ï¼ç¶å线ç¨Aè·å¾æ¶é´çï¼ç±äºä¹åå·²ç»è¿è¡äºhash碰æçå¤æï¼æææ¤æ¶ä¸ä¼åè¿è¡å¤æï¼èæ¯ç´æ¥è¿è¡æå ¥ï¼è¿å°±å¯¼è´äºçº¿ç¨Bæå ¥çæ°æ®è¢«çº¿ç¨Aè¦çäºï¼ä»è线ç¨ä¸å®å ¨ã
æ¹åï¼
æ°æ®ä¸¢å¤±ãæ»å¾ªç¯å·²ç»å¨å¨JDK1.8ä¸å·²ç»å¾å°äºå¾å¥½ç解å³ï¼å¦æä½ å»é 读1.8çæºç ä¼åç°æ¾ä¸å°HashMap#transfer()ï¼å 为JDK1.8ç´æ¥å¨HashMap#resize()ä¸å®æäºæ°æ®è¿ç§»ã
2ãHashMap线ç¨ä¸å®å ¨çä½ç°ï¼
JDK1.7 HashMap线ç¨ä¸å®å ¨ä½ç°å¨ï¼æ»å¾ªç¯ãæ°æ®ä¸¢å¤±
JDK1.8 HashMap线ç¨ä¸å®å ¨ä½ç°å¨ï¼æ°æ®è¦ç
多线程环境下,hashmap为什么会出现死循环?
在多线程环境下,HashMap出现死循环问题主要与JDK1.7版本的实现机制相关。当并发操作时,可能会导致闭合回路,引发CPU占用率%的问题。原因在于HashMap内部使用Entry数组存储数据。通过哈希算法获取数组索引,并在该位置插入元素。若发生冲突,则在数组位置生成链表。
在1.7版本中,当多线程环境下进行扩容操作,可能会产生循环链表或数据丢失现象。而在1.8版本中,虽然仍然可能发生数据覆盖的情况,但通过优化,新数组中链表顺序与旧数组保持一致。
Java8对HashMap的改进,不再在resize()方法中调用transfer()方法,而是直接在方法体内执行transfer()代码,从而避免了在多线程环境下可能引发的循环链表问题。
针对死循环问题,解决策略主要有三个:一是使用线程安全的ConcurrentHashMap替代HashMap,这是首选方案;二是使用线程安全的容器Hashtable,但其性能较低,不推荐使用;三是通过synchronized或Lock加锁实现多线程同步操作,虽然可以避免死循环,但会显著影响性能。
综上所述,推荐使用ConcurrentHashMap作为多线程环境下的替代方案,以避免死循环问题,提高系统稳定性与性能。
hashmap1.7和1.8的区别
HashMap是我们开发中经常使用到的集合,jdk1.8相对于1.7底层实现发生了一些改变。1.8主要优化减少了Hash冲突 ,提高哈希表的存、取效率。
底层数据结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构(当链表长度大于8,转为红黑树)。
JDK1.8中resize()方法在表为空时,创建表;在表不为空时,扩容;而JDK1.7中resize()方法负责扩容,inflateTable()负责创建表。
1.8中没有区分键为null的情况,而1.7版本中对于键为null的情况调用putForNullKey()方法。但是两个版本中如果键为null,那么调用hash()方法得到的都将是0,所以键为null的元素都始终位于哈希表table0中。
当1.8中的桶中元素处于链表的情况,遍历的同时最后如果没有匹配的,直接将节点添加到链表尾部;而1.7在遍历的同时没有添加数据,而是另外调用了addEntry()方法,将节点添加到链表头部。
1.7中新增节点采用头插法,1.8中新增节点采用尾插法。这也是为什么1.8不容易出现环型链表的原因。
1.7中是通过更改hashSeed值修改节点的hash值从而达到rehash时的链表分散,而1.8中键的hash值不会改变,rehash时根据(hash&oldCap)==0将链表分散。
1.8rehash时保证原链表的顺序,而1.7中rehash时有可能改变链表的顺序(头插法导致)。
在扩容的时候:
1.7在插入数据之前扩容,而1.8插入数据成功之后扩容。