采访合集:https://gitee.com/mydb/interview这篇文章的这个问题是开放式问题。除了死循环,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时间片运行out和enter处于休眠状态,线程T1开始执行扩容操作,直到线程T1的扩容完成后线程T2才被唤醒。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
