前言HashMap并发下的死循环问题在jdk1.7及之前的版本中都存在。jdk1.8已通过添加loHead和loTail进行修复。虽然已经修复,但是如果涉及到并发的情况下,一般建议使用CurrentHashMap而不是HashMap,保证不会出现线程安全问题。在jdk1.7及之前,HashMap在并发条件下存在循环问题。这种循环问题会导致服务器的cpu飙升到100%。为了解答这个疑惑,今天就来了解一下高并发条件下线程不安全的HashMap。它是如何导致无限循环的?探究hashmap死循环的原因,需要分析hashmap的源码,才能从根本上理解hashmap。在分析之前,我们需要知道,在jdk1.7及之前,HashMap采用数组+链表的数据结构,而在jdk1.8中,则采用数组+链表+红黑树的数据结构,进一步减少了哈希冲突导致的查询丢失。文本首先将元素插入哈希图中。这里,put方法会被调用publicVput(Kkey,Vvalue){if(table==EMPTY_TABLE){inflateTable(threshold);//分配数组空间}if(key==null)returnputForNullKey(value);inthash=hash(key);//进一步计算key的hashcode,保证hash是统一的inti=indexFor(hash,table.length);//得到表中的实际位置for(Entrye=table[i];e!=null;e=e.next){...}modCount++;//在保证并发访问的情况下,如果HashMap内部结构发生变化,则快速响应失败//Key注意这个addEntry方法添加元素addEntry(hash,key,value,i);返回空值;然后我们看这个addEntry方法,里面调用的resize()扩展方法就是今天的主角voidaddEntry(inthash,Kkey,Vvalue,intbucketIndex){if((size>=threshold)&&(null!=table[bucketIndex])){resize(2*table.length);//当size超过临界阈值threshold时,很快就会发生散列冲突时扩容。扩容后新容量是旧容量的两倍hash=(null!=key)?散列(键):0;bucketIndex=indexFor(hash,table.length);//扩容后重新计算插入位置下标}//put将元素放入HashMapbucket对应的位置createEntry(hash,key,value,bucketIndex);接下来我们进入resize()方法,然后揭开里面的transfer()方法。这个方法也是死循环的罪魁祸首//根据新的容量扩展Hash表voidresize(intnewCapacity){Entry[]oldTable=table;//旧数据intoldCapacity=oldTable.length;//获取旧的capacityvalueif(oldCapacity==MAXIMUM_CAPACITY){//旧容量值已达到最大容量值threshold=Integer.MAX_VALUE;//修改扩容阈值return;}//新建结构体Entry[]newTable=newEntry[newCapacity];//添加旧表复制数据到新结构transfer(newTable,initHashSeedAsNeeded(newCapacity));table=newTable;//修改HashMap底层数组threshold=(int)Math.min(newCapacity*loadFactor,MAXIMUM_CAPACITY+1);//修改Threshold}最后我们仔细分析下transfer()方法//复制数据在旧表到新结构voidtransfer(Entry[]newTable,booleanrehash){intnewCapacity=newTable.length;//Capacityfor(Entrye:table){//遍历所有桶while(null!=e){//遍历桶(是一个链表)中的所有元素Entrynext=e.next;if(rehash){//如果是re-Hash,需要重新计算hash值e.hash=null==e.key?0:散列(e.key);}inti=indexFor(e.hash,newCapacity);//定位Hashbuckete.next=newTable[i];//元素连接到bucket,相当于单链表的插入,always插入在最前面newTable[i]=e;//newTable[i]的值永远是最新插入的值e=next;//继续下一个元素}}}添加的元素达到阈值后,hashmap是经过处理扩容的,使用reaize方法。在扩展HashMap时,会调用transfer()来传输旧的hashmap中的元素。那么我们今天要探讨的死循环问题就发生在这个方法中。转账时,转账方法中会调用如下四行代码Entrynext=e.next;e.next=newTable[i];newTable[i]=e;e=next;将元素插入到新的HashMap中粗略看了这四行代码,好像没什么问题。元素传递示意图如下(线程不冲突时)。然后我们让线程A和B同时访问我的代码。当线程A执行到如下代码时,Entrynext=e.next;线程A交出时间片,此时线程B接管传输,完成元素的传输。此时线程A再次获取时间片,执行完后执行代码如图,当e=a时,此时执行e.next=newTable[i];//元素a指向元素b,导致循环在链表中产生循环后,当get()方法获取的元素恰好落在这个循环链表上时,线程会一直遍历循环,无法跳转out,导致cpu飙升100%!综上所述,在多线程的情况下尽量不要使用HashMap。您可以改用线程安全的哈希表,例如ConcurrentHashMap、HashTable、Collections.synchronizedMap()来避免多线程安全问题。/感谢大家的支持/以上就是本次分享的全部内容,希望对大家有所帮助^_^喜欢的话别忘了三连分享、点赞、收藏哦~欢迎关注公众号ProgrammerBus,有趣、有品位、有温度的程序员Bus,涉足大厂面授体验、程序员生活、实战教程、前沿技术等,关注我,交友!