concurrenthashmap 替换需区分安全场景与必须重写逻辑:基础单操作可直接替换,但条件写入、懒加载、计数等须改用原子方法;注意 null 限制、size 估算性及 compute 耗时风险。

直接替换不是最稳妥的做法,关键在理解哪些行为能安全过渡、哪些必须改写逻辑。
哪些地方可以原样替换
如果代码里只用到基础的 put、get、remove、containsKey 这类单操作,且没有手动同步或依赖 HashMap 的非线程安全特性(比如靠异常判断是否存在),那把声明和初始化从 new HashMap() 换成 new ConcurrentHashMap() 就行。
- 读操作(
get、containsKey)本身无锁,性能不降反升 - 写操作(
put、remove)自动加锁到桶级别,不会互相阻塞 - 迭代器是弱一致性,不抛
ConcurrentModificationException,遍历时允许其他线程修改
哪些地方必须重写逻辑
以下模式看似简单,但在 HashMap 里靠运气能跑通,在 ConcurrentHashMap 里会出错或结果不可靠,必须换成原子方法:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
-
if (!map.containsKey(k)) map.put(k, v)→ 改用map.putIfAbsent(k, v) -
v = map.get(k); if (v == null) { v = compute(); map.put(k, v); }→ 改用map.computeIfAbsent(k, key -> compute()) - 计数累加:
map.put(k, map.getOrDefault(k, 0) + 1)→ 改用map.merge(k, 1, Integer::sum)或map.compute(k, (k1, v) -> (v == null ? 0 : v) + 1)
容易被忽略的细节
ConcurrentHashMap 不支持 null 作为 key 或 value,这点和 HashMap 不同,运行时报 NullPointerException 是常见翻车点。
- 检查所有 put 和 compute 方法传入的 key/value 是否可能为 null
-
size()和isEmpty()返回的是估算值,高并发下不准;如需精确统计,得自己用mappingCount()(返回 long,更准但仍是近似)或另加计数器 - 扩容是协作式的,不阻塞读写,但
compute类方法内部会锁住对应桶,别在里面做耗时操作(比如远程调用、sleep)
上线前建议做两件事
避免“换了就完事”,真正落地要验证是否真安全:
- 用多线程压测工具(如 JMeter + 自定义线程组)模拟并发 put/get/remove 混合场景,观察是否出现数据丢失、重复或 size 异常
- 检查日志中是否有
NullPointerException报在 map 操作上,重点排查 null key/value 的来源
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










