这篇文章中的这个问题是一个开放问题。除了死循环,HashMap还有哪些问题呢?一般来说,HashMap的所有“问题”都是由于使用不当造成的(HashMap)。问题大致分为两类:程序问题:比如HashMap的JDK1.7中,在并发插入的时候可能会出现死循环或者数据覆盖的问题。业务问题:比如HashMap的乱序导致查询结果与预期结果不一致。接下来,让我们一一来看。1.死循环问题死循环问题出现在JDK1.7版本。原因是JDK1.7的HashMap使用了header插入方式,在并发扩展的时候可能会出现死循环问题。具体过程如下所示。正常情况下HashMap的扩展如下图所示:旧HashMap的节点会依次转移到新HashMap中,旧HashMap的转移顺序为A、B、C,新HashMap使用head插入法,所以最终在新的HashMap中HashMap中的顺序是C,B,A,如上图所示。有了这些前置知识,我们来看看死循环是怎么诞生的?1.1死循环执行过程死循环是由HashMap并发扩容引起的。第一步并发扩容,线程T1和线程T2需要对HashMap进行扩容,此时T1和T2指向链表的头节点元素A,T1和T2的下一个节点,即T1.next和T2.next指向节点B,如下图所示:1.2无限循环死循环执行过程的第二步是线程T2在时间片耗尽时进入休眠状态,线程T1开始执行扩展操作。直到线程T1的扩展完成后,线程T2才被唤醒。展开后的场景如下图所示:从上图可以看出,线程T1执行后,HashMap的顺序因为头部插入方式发生了变化,但是线程T2对发生的一切是不可知的,所以它指向的元素不变,如上图同理,T2指向A元素,T2.next指向的节点就是B元素。1.3死循环执行过程3当线程T1执行完毕,线程T2重新开始执行时,就建立了一个死循环,如下图:因为T1执行完展开后,节点B的下一个节点是A,线程T2指向第一个节点是A,第二个节点是B,这个顺序和T1展开后的节点顺序刚好相反。T1执行完之后,顺序是从B到A,T2的顺序是从A到B,这样A节点和B节点就形成了死循环,这就是HashMap死循环的原因。1.4解决方案使用线程安全的容器代替HashMap,比如ConcurrentHashMap或者Hashtable,因为ConcurrentHashMap的性能远高于Hashtable,所以推荐使用ConcurrentHashMap代替HashMap。2.数据覆盖问题数据覆盖问题出现在并发添加元素的场景。它不仅出现在JDK1.7版本中,在其他版本中也存在。数据覆盖的过程是这样的:线程T1在添加的时候,判断了一个位置可以插入元素,但是在真正进行插入操作之前,时间片就用完了。线程T2也进行了加法运算,T2生成的哈希值与T1相同,即T2将存储的位置与T1相同,因为该位置还没有插入值(T1线程已经执行了一半),所以T2把自己的值存到当前位置。T1恢复执行后,因为已经执行了非空判断,没有感知到这个位置已经有值了,所以把自己的值插入到这个位置,然后覆盖T2的值。具体执行过程如下图所示。2.1数据覆盖执行过程一个线程T1准备往Null中插入数据k1:v1,但还没有真正执行,自己的时间片用完了,进入休眠状态,如下图:2.2数据覆盖执行过程2线程T2准备将数据k2:v2插入到Null中,因为这里没有值。如果这里有值,它会使用chain的方式将数据插入到下一个没有值的位置,但是经过判断如果这里没有值,则直接插入数据,如下图:2.3数据覆盖执行流程三个线程T2执行完毕后,线程T1恢复执行,因为线程T1之前已经判断这个位置没有值,所以会直接插入,此时线程T2插入的值会被覆盖时间,如下图所示:2.4解决方案解决方案同第一种解决方案,使用ConcurrentHashMap代替HashMap即可解决该问题。3、乱序问题这里的乱序问题是指HashMap的添加和查询的顺序不一致,导致程序执行的结果与程序员预期的结果不一致,如下代码所示:String,String>map=newHashMap<>();//添加元素for(inti=1;i<=5;i++){map.put("2022-10-"+i,"Hello,Java:“+我);}//查询元素map.forEach((k,v)->{System.out.println(k+":"+v);});我们添加的顺序:我们期望查询的顺序和添加的顺序是一致的,但是上面代码输出的结果是:执行结果与我们预期的结果不符,这就是HashMap的乱序问题。我们期望输出是Hello,Java1,2,3,4,5,但我们得到的序列是2,1,4,3,5。解决方案解决HashMap乱序的问题,我们只需要将HashMap换成LinkedHashMap即可,如下代码所示:LinkedHashMap
