HashMap 与 ConcurrentHashMap 有什么区别?通俗易懂版

原文来自于:zha-ge.cn/java/21

HashMap 与 ConcurrentHashMap 有什么区别?通俗易懂版

引子:一次线上血案

那天下午,阳光正好,我正在悠闲地喝着咖啡,突然钉钉疯狂响起------线上系统崩了!用户疯狂投诉说页面卡死,数据丢失。

打开监控一看,CPU 使用率飙到 100%,线程全部卡在一个看似无害的 HashMap 操作上。我心里咯噔一下:完了,又是并发问题!

这让我想起了一个经典的比喻:HashMap 就像一个普通的记事本,一个人用没问题,但如果多个人同时在上面写字,那就乱套了。

探索:HashMap 的"单线程世界"

HashMap 在单线程环境下表现完美,就像一个勤劳的管家:

java 复制代码
// HashMap 的基本操作,单线程下很安全
Map<String, String> userCache = new HashMap<>();
userCache.put("user123", "张三");
String userName = userCache.get("user123");
// 一切都很美好...直到并发来了

但在多线程环境下,HashMap 就像一个没有交通规则的十字路口------车辆(线程)从四面八方涌来,撞车(数据竞争)是必然的。

我开始翻阅源码,发现 HashMap 的 resize() 方法在并发时会形成环形链表,导致 CPU 100% 的死循环。这就是著名的"HashMap 死循环"问题。

转折:踩坑瞬间

当时我天真地想:"既然有并发问题,那我加个 synchronized 不就行了?"

java 复制代码
// 我的第一次"聪明"尝试
Map<String, String> userCache = new HashMap<>();
synchronized(userCache) {
    userCache.put("user456", "李四");
}
synchronized(userCache) {
    String userName = userCache.get("user456");
}

结果发现性能直接跪了!就像在高速公路上设置红绿灯,所有车辆都得排队等待,吞吐量暴跌 80%。

更要命的是,如果忘记在某个地方加 synchronized,程序依然会出问题。这种方案就像在雷区走路,一不小心就踩雷。

解决:ConcurrentHashMap 闪亮登场

这时候,ConcurrentHashMap 就像一个经验丰富的交通管制员出现了。它采用了"分段锁"的设计理念:

  • HashMap:整个十字路口只有一个红绿灯,所有方向都得等
  • ConcurrentHashMap:把十字路口分成多个小区域,每个区域独立管理
java 复制代码
// ConcurrentHashMap:并发安全且高效
ConcurrentHashMap<String, String> userCache = new ConcurrentHashMap<>();
// 多个线程可以同时操作不同的段
userCache.put("user789", "王五");  // 线程 1
String userName = userCache.get("user123");  // 线程 2 同时执行
// 和谐共处,性能还很棒

ConcurrentHashMap 的核心优势:

特性 HashMap ConcurrentHashMap
线程安全
并发性能 N/A 优秀
空值支持
迭代器 快速失败 弱一致性

经验启示

经过这次血泪教训,我总结了几个关键点:

什么时候用 HashMap?

  • 单线程环境:性能最优,无脑选择
  • 不可变数据:初始化后不再修改
  • 局部变量:方法内部使用,天然线程安全

什么时候用 ConcurrentHashMap?

  • 多线程环境:必选项,没得商量
  • 高并发场景:读写操作频繁
  • 缓存场景:多个线程共享数据

踩坑提醒

  1. 别想着给 HashMap 加锁:性能损失巨大,还容易出错
  2. 注意 null 值:ConcurrentHashMap 不允许 null 键或值
  3. 迭代器行为 :ConcurrentHashMap 的迭代器不会抛 ConcurrentModificationException

总结

这次经历让我深刻理解了一个道理:选择合适的工具,比优化代码更重要

HashMap 就像跑车,在赛道上飞驰;ConcurrentHashMap 就像越野车,在复杂地形中稳定前行。关键是要在合适的场景选择合适的工具。

现在每当有新人问我这个问题,我都会告诉他们:单线程用 HashMap,多线程用 ConcurrentHashMap,简单粗暴,不会错!

记住,程序员的智慧不在于写出多复杂的代码,而在于选择最合适的解决方案。毕竟,生产环境的稳定性,比任何炫技都重要。

相关推荐
带刺的坐椅11 分钟前
(对标 Spring IA 和 LangChain4j)Solon AI & MCP v3.7.0, v3.6.4, v3.5.8 发布(支持 LTS)
java·spring·ai·solon·mcp·langchain4j
7澄113 分钟前
深入解析 LeetCode 1572:矩阵对角线元素的和 —— 从问题本质到高效实现
java·算法·leetcode·矩阵·intellij-idea
诗9趁年华16 分钟前
缓存三大问题深度解析:穿透、击穿与雪崩
java·spring·缓存
阳光明媚sunny17 分钟前
分糖果算法题
java·算法
whltaoin17 分钟前
【JAVA全栈项目】弧图图-智能图床SpringBoot+MySQL API接口结合Redis+Caffeine多级缓存实践解析
java·redis·spring·缓存·caffeine·多级缓存
一 乐31 分钟前
医疗管理|医院医疗管理系统|基于springboot+vue医疗管理系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·医疗管理系统
华仔啊1 小时前
SpringBoot 2.x 和 3.x 的核心区别,这些变化你必须知道
java·spring boot·后端
laocooon5238578861 小时前
大数的阶乘 C语言
java·数据结构·算法
不见长安在1 小时前
分布式ID
java·分布式·分布式id
熊小猿1 小时前
Redis 缓存怎么更新?—— 四种模型与一次“迟到的删除”
java·后端·spring