concurrenthashmap线程安全但需正确使用:避免if-put竞态,改用putifabsent等原子方法;get无锁且高效,勿加锁;初始化容量应预估并设为2的幂次;遍历接受弱一致性,强一致性需求表明设计有误。

ConcurrentHashMap 本身线程安全,但用错方式照样出问题。关键不是“能不能用”,而是“怎么用才真正安全高效”。它靠细粒度锁、无锁读和原子操作协同工作,不是靠加锁兜底。
别写 if-put 这类非原子组合
像 if (!map.containsKey(k)) map.put(k, v) 看似合理,实际是竞态高发区:两个线程同时判断 key 不存在,接着都执行 put,后写覆盖前写。这种逻辑必须替换成原子方法:
- putIfAbsent(k, v):key 不存在才插入,一次 CAS 完成
- computeIfAbsent(k, f):key 不存在时调用函数计算值并写入,函数只被一个线程执行(适合查库加载缓存)
- merge(k, v, (old, new) -> ...):安全累加,比如计数器自增
- replace(k, oldValue, newValue):带旧值校验的更新,防止误覆盖
读操作直接 get,千万别加锁
get() 是无锁操作,依赖 volatile 字段保证可见性,性能接近 HashMap。在缓存、配置读取等读多写少场景下,map.get(key) 就够了。如果给 get 加 synchronized 或 ReentrantLock,反而把并发读变成串行,吞吐量断崖下跌。
初始化容量要预估到位
默认初始容量 16,百万级键值对下频繁扩容会触发协作迁移,带来额外开销甚至卡顿。建议按预估总键数向上取整到最接近的 2 的幂次,并预留余量:
- 预计存 200 万个 key → 初始容量设为
new ConcurrentHashMap(2097152) - 避免运行时扩容;不推荐调大负载因子(如设 0.9),会加剧哈希冲突,链表变长,拖慢性能
遍历时接受弱一致性
ConcurrentHashMap 的迭代器是弱一致的:不抛 ConcurrentModificationException,但可能漏掉刚插入的项,或看到已删除项的旧值。这对监控统计、日志输出、后台扫描等非强一致性场景完全适用。推荐使用:
map.forEach((k, v) -> {...})-
map.entrySet().stream()...(注意流式遍历也遵循弱一致性)
若需强一致性,说明设计本身有问题——应换用其他机制,而非强行让 ConcurrentHashMap 承担它不承诺的语义。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











