应使用concurrenthashmap替代hashmap:多线程读写共享map时,因其采用cas+桶级锁实现细粒度并发控制,支持原子复合操作,避免数据错乱、死循环及性能瓶颈。

在多线程环境下,直接用 HashMap 会因非线程安全导致数据错乱、死循环甚至 CPU 占用飙升;ConcurrentHashMap 是专为并发设计的替代方案,它不锁整个表,性能更好,也更安全。
什么时候该换?
当你遇到以下情况时,就该考虑用 ConcurrentHashMap 替代 HashMap:
- 多个线程同时读写同一个 map(比如缓存、计数器、共享配置)
- 用了
Collections.synchronizedMap(new HashMap()),但发现写操作变慢、竞争严重 - 代码中频繁调用
put、get、computeIfAbsent等方法,且不能接受加显式锁
怎么改?基础替换很简单
把声明和初始化从 HashMap 换成 ConcurrentHashMap,其余 API 大部分保持一致:
// 原来这样 Map<string integer> map = new HashMap(); // 改成这样(推荐使用接口类型) Map<string integer> map = new ConcurrentHashMap();</string></string>
注意:ConcurrentHashMap 不支持 null 作为 key 或 value(会抛 NullPointerException),这点和 HashMap 不同,改的时候要检查业务逻辑是否依赖 null 值。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
别只换构造,要用对并发方法
单纯替换构造器只是第一步。真正发挥优势,得用它的原子性方法:
-
computeIfAbsent(key, mappingFunction):线程安全地“有则取,无则算并存”,适合缓存加载 -
merge(key, value, remappingFunction):安全合并值,比如累加计数 -
putIfAbsent(key, value):仅当 key 不存在时插入,比先get再put更可靠 - 避免用
containsKey() + get()这类非原子组合,改用getOrDefault()或上面的原子方法
还要注意这些细节
ConcurrentHashMap 的行为和 HashMap 并不完全等价,容易踩坑:
- 迭代器是弱一致性(weakly consistent):不抛
ConcurrentModificationException,可能看到旧值或跳过新值,但不会崩溃——适合遍历做只读统计,不适合边遍历边删改 - 不支持
keySet().iterator().remove()这类操作,删除要用remove(key)或compute() -
size()返回的是估算值(可能滞后),如需精确计数,建议用mappingCount()(返回long,更准确) - 默认并发级别已自动优化(JDK 8+),一般不用手动设
concurrencyLevel
不复杂但容易忽略:换完之后,重点检查 null 处理、迭代逻辑和复合操作,再配合原子方法,就能稳稳扛住并发压力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










