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

蔚来测开一面: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.xdnf.cn/news/15136.html

相关文章:

  • Spring的事务控制——学习历程
  • HarmonyOS NEXT端云一体化开发初体验
  • [Dify] -基础入门4-快速创建你的第一个 Chat 应用
  • 牛客:HJ17 坐标移动[华为机考][字符串]
  • Leaflet面试题及答案(1-20)
  • [实战]调频三角波和锯齿波信号生成(完整C代码)
  • 深入浅出:什么是MCP(模型上下文协议)?
  • 力扣网编程134题:加油站(双指针)
  • C++中柔性数组的现代化替代方案:从内存布局优化到标准演进
  • Debian:从GNOME切换到Xfce
  • 扫描文件 PDF / 图片 纠斜 | 图片去黑边 / 裁剪 / 压缩
  • I2C集成电路总线
  • Semi-Supervised Single-View 3D Reconstruction via Prototype Shape Priors
  • 基于Java Spring Boot开发的旅游景区智能管理系统 计算机毕业设计源码32487
  • linux网络编程之单reactor模型(一)
  • Python 数据建模与分析项目实战预备 Day 2 - 数据构建与字段解析(模拟简历结构化数据)
  • 【前端】【组件库开发】【原理】【无框架开发】现代网页弹窗开发指南:从基础到优化
  • GNhao,获取跨境手机SIM卡跨境通信新选择!
  • 手机恢复出厂设置怎么找回数据?Aiseesoft FoneLab for Android数据恢复工具分享
  • Java中的泛型继承
  • 深度学习篇---昇腾NPUCANN 工具包
  • 《Java EE与中间件》实验三 基于Spring Boot框架的购物车
  • BLOB 数据的插入与读取详解
  • Linux驱动学习day22(interrupt子系统)
  • [python]在drf中使用drf_spectacular
  • 卢比危机下的金融破局:科伦坡交易所技术升级作战图
  • SpringBoot JWT
  • NFS文件存储及论坛项目搭建(php)
  • Web攻防-SSTI服务端模版注入利用分类语言引擎数据渲染项目工具挖掘思路
  • MCU芯片内部的ECC安全机制