当前位置: 首页 > news >正文

蔚来测开一面:HashMap从1.7开始到1.8的过程,既然都解决不了并发安全问题,为什么还要进一步解决环形链表的问题?

文章目录

    • 问题的根源:JDK 1.7 的设计缺陷
    • 为什么必须解决这个问题?
      • 1\. 故障等级完全不同 💣
      • 2\. JDK 1.8 的解决方案:一石二鸟 🦅
      • 3\. 为“不小心”的开发者提供一层保障 🛡️
    • 结论

这是一个非常好的问题,它直击了技术演进的核心: 即使不能解决所有问题,也要优先解决最致命的问题

简单来说,解决环形链表问题,并不是为了实现线程安全,而是为了消除一个在并发场景下会导致服务器CPU 100%直至宕机的“定时炸弹”

这是一个关于**故障严重性(Failure Severity)**的权衡问题。


问题的根源:JDK 1.7 的设计缺陷

在 JDK 1.7 中,HashMap 扩容(resize)时转移数据的 transfer 方法使用了头插法

  • 头插法:在将旧数组的元素转移到新数组时,新来的元素总被放在链表的头部。
  • 问题所在:在单线程下,头插法会使链表顺序反转,这没问题。但在多线程并发扩容时,两个线程可能同时操作同一个链表。一个线程执行一半被挂起,另一个线程完成了扩容导致链表反转。当第一个线程恢复执行时,它会基于一个已经改变的链表继续操作,这会导致链表节点的 next 指针互相指向,最终形成一个环形链表

这个环形链表一旦形成,后续对该位置的 get() 操作就会陷入无限循环,导致CPU占用率飙升到100%,整个应用或服务器都会被拖垮。


为什么必须解决这个问题?

现在回到你的核心问题:既然HashMap本来就不是线程安全的,并发使用时数据丢失、不一致等问题都可能发生,为什么还要专门修复环形链表这个bug?

1. 故障等级完全不同 💣

HashMap 在并发下可能遇到的问题可以分为两类:

  • 数据不一致 (Data Inconsistency): 比如两个线程同时 put,一个线程的数据覆盖了另一个,导致数据丢失。这属于数据问题,虽然也很糟糕,但通常不会让整个应用程序崩溃。
  • 致命的系统崩溃 (Fatal System Crash): 环形链表导致的无限循环,会耗尽CPU资源,引起拒绝服务(DoS)。这是一个系统级的灾难性故障。

打个比方:
数据不一致就像是两个售票员卖了同一张电影票,会导致顾客(数据)冲突,需要业务逻辑去处理。
而环形链表就像是售票系统的后台代码进入了死循环,整个售票系统都瘫痪了,谁也买不了票。

显然,消除一个能让服务器宕机的Bug,其优先级远高于处理一般的数据不一致问题

2. JDK 1.8 的解决方案:一石二鸟 🦅

JDK 1.8 对 HashMap 进行了重大重构,主要做了两件事,顺便解决了环形链表问题:

  • 引入红黑树 (Red-Black Tree): 这是1.8最大的性能优化。当链表长度超过一定阈值(默认为8)时,链表会转化为红黑树,将该位置的查找时间复杂度从 O(n) 优化到 O(log n)。这是重构的主要动机之一。

  • 修改扩容算法为“尾插法”: 在数据转移时,保持链表元素的原有顺序,将元素依次插入到新链表的尾部。因为顺序保持不变,就不会出现1.7中指针反转交错形成环路的情况。这个修改顺手就根除了环形链表这个“定时炸弹”

所以,JDK 团队在进行性能优化的同时,也修复了这个已知的、非常严重的设计缺陷。

3. 为“不小心”的开发者提供一层保障 🛡️

虽然官方文档明确指出 HashMap 非线程安全,但现实中总有开发者会误用或者在一些自认为安全的场景下不慎造成并发。

与其留着一个会导致服务器崩溃的“地雷”,不如让它在被误用时表现得“温和”一些。JDK 1.8 之后,即使你在并发场景下误用了 HashMap,最可能发生的是数据丢失,而不再是整个应用的崩溃。这是一种更“安全失败”(Fail-Safe)的设计哲学。


结论

总而言之,从1.7到1.8的演进,解决环形链表问题并非为了让 HashMap 变得线程安全,而是:

  1. 拆除了一个会导致系统崩溃的“核弹级”Bug。
  2. 作为性能优化(引入红黑树)过程中的一个必然修复。
  3. 降低了误用HashMap时带来的风险,使框架更健壮。

面试官问这个问题,就是想考察你是否理解技术决策背后的权衡和深层原因,而不仅仅是背诵“1.7是头插法,1.8是尾插法和红黑树”。

正确的并发选择:在需要保证线程安全的场景下,我们应该始终使用 ConcurrentHashMap

http://www.lryc.cn/news/585635.html

相关文章:

  • 前端面试专栏-算法篇:23. 图结构与遍历算法
  • USB一线连多屏?Display Link技术深度解析
  • React中Redux基础和路由介绍
  • 适配多场景,工业显示器让操作更高效
  • 前端八股-promise
  • Spring的事务控制——学习历程
  • C++设计秘籍:为什么所有参数都需类型转换时,非成员函数才是王道?
  • Python-正则表达式-信息提取-滑动窗口-数据分发-文件加载及分析器-浏览器分析-学习笔记
  • (补充)RS422
  • Qt 实现新手引导
  • 分布式推客系统全栈开发指南:SpringCloud+Neo4j+Redis实战解析
  • 【世纪龙科技】几何G6新能源汽车结构原理教学软件
  • 【龙泽科技】新能源汽车维护与动力蓄电池检测仿真教学软件【吉利几何G6】
  • 重构下一代智能电池“神经中枢”:GCKontrol定义高性能BMS系统级设计标杆
  • Java :T extends Comparable<? super T> 和 T extends Comparable<T>的区别
  • 李沐动手学深度学习Pytorch-v2笔记【07自动求导代码实现】
  • 标准化模型格式ONNX介绍:打通AI模型从训练到部署的环节
  • 第十五章 STL(stack、queue、list、set、map容器使用)
  • Nginx 添加 Stream 模块(不覆盖已安装内容)
  • Java 中使用 Stream 将 List 转换为 Map 实战笔记(生产级版)
  • 【Freertos实战】零基础制作基于stm32的物联网温湿度检测(教程非常简易)持续更新中.........
  • 计算机网络第三章(5)——数据链路层《广域网》
  • 【网络编程】KCP——可靠的 UDP 传输协议——的知识汇总
  • 触控屏gt1947
  • 数据治理到底是什么?搞清这四件事,你就彻底明白了!
  • 【C++】内联函数inline以及 C++入门(4)
  • 静态路由综合配置实验报告
  • python实现DoIP基本通信(收发报文)
  • 深入探索Kafka Streams:企业级实时数据处理实践指南
  • 外媒:蚂蚁数科等科技公司在香港数字资产枢纽建设中显身手