哈喽,你们好,我是老王,欢迎来到 Java 面试突击,咱们今天来开始第 6 期的内容。
本期的问题是:HashMap 为什会致使 CPU 运行 100%?这是一个比较常见的经典问题了,可是有不少人读者朋友给我反馈,尼玛,看文章根本看不懂啊?Sun 公司都不知道这个问题的缘由吧?不,是 Oracle 公司都不知道这个问题的缘由吧?面试官怕也不知道这个的答案吧?
咳咳,做为一个很正经的面试官,我以为这个问题一点都不重要,重要的是你不知道答案啊。好的,下一位面试者请进,您先回去等通知吧。
为了不这种尴尬的事情发生,今天咱们来好好聊一下这个问题,毕竟技能再手,才能吊打面试官不是?
正文面试
这个问题相关的知识点,有如下几个:
H```
ashMap 的底层数据结构是什么?
什么是哈希碰撞?如何该解决这个问题?
什么是扩展因子?它有什么用?
还有对 HashMap 源码的理解,为何 HashMap 会致使死循环?数组
视频版答案 视频内容以下: 图文答案 1.HashMap 的底层数据结构 先来讲 HashMap 的底层数据结构,看过 HashMap 的源码咱们就会发现,JDK 1.7 和 JDK 1.8 HashMap 的组成是不一样的,JDK 1.7 HashMap 的组成是数组 + 链表的形式,而 JDK 1.8 新增了红黑树的数据结构,当 HashMap 中的链表长度大于 8 时,链表结构就会转换为红黑树,以下图所示:  2.哈希碰撞及解决方案 所谓的哈希碰撞指的是不一样的值,通过哈希以后获得的值确是相同的,这种状况就叫作哈希碰撞或哈希冲突。解决哈希碰撞的经常使用方法是:开放定址法和链表地址法,而 HashMap 采用的就是链表地址法。它的实现原理就是将 HashMap 中相同的哈希值以链表的形式存储起来。 3.扩展因子 扩展因子也叫加载因子或负载因子是 HashMap 中的一个属性,以下图所示:假如数组的默认长度为 10,扩展因子为 0.5,那么当数组超过 10*0.5=5 个时,HashMap 就会扩容为以前容量的两倍,因此说扩展因子就是用来断定 HashMap 是否知足扩容条件的。 4.HashMap死循环分析 HashMap 致使 CPU 100% 的缘由就是由于 HashMap 死循环致使的,那 HashMap 是如何形成死循环的?接下来咱们一块儿来看。 以 JDK 1.7 为例,假设 HashMap 的默认大小为 2,HashMap 自己中有一个键值 key(5),咱们再使用两个线程:t1 添加 key(3),t2 添加 key(7),首先两个线程先把 key(3) 和 key(7) 都添加到 HashMap 中,此时由于 HashMap 的长度不够用了就会进行扩容操做,而后这时线程 t1 在执行到 Entry<K,V> next = e.next; 时,交出了 CPU 的使用权,源代码以下:
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;
}
}
}安全
那么此时线程 t1 中的 e 指向了 key(3),而 next 指向了 key(7) ;以后线程 t2 从新 rehash 以后链表的顺序被反转,链表的位置变成了 key(5) -> key(7) -> key(3),其中 “->” 用来表示下一个元素,当 t1 从新得到执行权以后,先执行 newTalbe[i] = e 把 key(3) 的 next 设置为 key(7),而下次循环时查询到 key(7) 的 e.next 为 key(3),因而就形 成了 key(3) 和 key(7) 的环形引用,就致使了死循环的产生,以下图所示:  HashMap 发生死循环的一个重要缘由是 JDK 1.7 时链表的插入是首部倒序插入的,而 JDK 1.8 时已经变成了尾部插入,有人把这个死循环的问题反馈给了 Sun 公司,但它们认为这不是一个问题,由于 HashMap 自己就是非线程安全的,若是要在多线程使用建议使用 ConcurrentHashMap 替代 HashMap,但面试中这个问题被问的频率比较高,因此在这里就特殊说明一下。 小结 HashMap 是非线程安全的,以 JDK 1.7 为例,当多线程并发扩容时就会出现环形引用的问题,从而致使死循环的出现,一直死循环就会致使 CPU 运行 100%,因此在多线程使用时,咱们须要使用 ConcurrentHashMap 来替代 HashMap,但只有懂得其中的因果关系才能吊打面试官,好了,本节内容到这里就结束了,咱们下期再见。 上期中奖名单:皮卡皮卡、一步、好好学习、谈笑、包子有话要讲。 以上中奖的朋友,请加个人微信:GG_Stone 领取奖励。 【END】 近期热文 * 面试突击 005 | Redis 是如何实现高可用的?它的实现方式有哪些? * 面试突击 004 | 如何排查 Redis 中的慢查询?视频实战篇 * 面试突击 003 | Redis 如何实现查询附近的人? * 面试突击 002 | Redis 是如何处理已过时元素的? * 面试突击 001 | Redis 如何从海量数据中查询出某一个 Key? * Java面试详解(2020版):500+ 面试题和核心知识点详解 关注下方二维码,订阅更多精彩内容 