hashmap不适合并发累加,因其非线程安全:扩容易致环形链表或数据覆盖;get+put为非原子操作,存在竞态条件;单synchronized put无法保证整个累加逻辑原子性。

Java 中的 HashMap 本身不是线程安全的,直接在多线程环境下用于并发累加(如统计频次、计数器)会导致数据丢失、死循环(JDK 7)、或 ConcurrentModificationException(JDK 8+),因此不能直接用 HashMap 实现可靠的并发累加计数器。
为什么 HashMap 不适合并发累加?
原因包括:
- put 操作可能触发扩容(resize),而扩容过程涉及数组复制和链表/红黑树重哈希,在多线程下极易产生环形链表(JDK 7)或数据覆盖;
- 多个线程同时执行
map.get(key)+map.put(key, value + 1)是典型的“读-改-写”非原子操作,存在竞态条件(race condition); - 即使使用
synchronized包裹单个 put,若未同步整个累加逻辑(get + increment + put),仍会出错。
推荐方案:用线程安全的替代结构
根据场景选择更合适、更高效的并发计数方案:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
ConcurrentHashMap:JDK 5+ 提供的线程安全哈希表,支持高并发读写。对累加场景,优先使用
computeIfAbsent或merge方法,它们内部已做原子控制:
// 推荐:利用 merge 原子累加(key 不存在时设为 1,存在则 +1)
concurrentMap.merge(key, 1, Integer::sum); -
LongAdder / AtomicInteger:如果只统计**全局一个总数**(而非按 key 分组),用
LongAdder(JDK 8+,高性能累加器)或AtomicInteger更轻量:
LongAdder counter = new LongAdder();
counter.increment(); // 线程安全,比 synchronized 或 AtomicLong 更快 -
ConcurrentHashMap + computeIfPresent / compute:适用于需要更复杂更新逻辑的场景,例如带条件判断的累加:
map.compute(key, (k, v) -> v == null ? 1 : v + 1);
不推荐但能“凑合用”的方式(仅限学习或极低并发)
以下方式存在明显缺陷,仅作对比理解,生产环境应避免:
- Collections.synchronizedMap(new HashMap()):加了全局锁,吞吐量低,且 get+put 组合仍需额外同步;
- 手动 synchronized 块包裹整个累加逻辑:锁粒度大,严重限制并发性,失去 HashMap 的优势;
- 用 ReentrantLock 显式加锁:同样锁粒度难控制,易写错,不如直接用 ConcurrentHashMap。
实际建议:按需求选型
总结选择逻辑:
- 要按 key 统计频次(如单词计数、用户行为汇总)→ 用
ConcurrentHashMap+merge或compute; - 只关心总次数(无 key 维度)→ 用
LongAdder(高并发)或AtomicInteger(简单场景); - 已有遗留代码强依赖 HashMap 接口 → 不要强行“修复”,应重构为 ConcurrentHashMap;
- 对一致性要求极高(如金融计数),且 key 数量可控 → 可考虑分段锁或
ConcurrentHashMap配合StampedLock读优化,但通常没必要。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










