ConcurrentHashMap 更适合高并发,因其采用分段锁(Java 7)或CAS+细粒度synchronized(Java 8+),读操作无锁、写操作仅锁单个Node,而Hashtable和synchronizedMap全局锁导致严重瓶颈。

ConcurrentHashMap 为什么比 Hashtable 和 synchronizedMap 更适合高并发
因为 ConcurrentHashMap 不是简单地给整个表加锁,而是把哈希桶(segment 或 node 链)拆分成多个独立的锁段,读操作完全无锁,写操作只锁定局部桶。而 Hashtable 所有方法都用 synchronized 修饰,Collections.synchronizedMap() 也默认锁整个 map 实例——在多线程频繁写入时,它们会成为明显瓶颈。
Java 8+ 的 ConcurrentHashMap 改用 CAS + synchronized 锁单个 Node(或红黑树根节点),进一步缩小锁粒度;同时支持更高并发度的迭代器(弱一致性,不抛 ConcurrentModificationException)。
- 不要在初始化时盲目调大
initialCapacity:它只是影响初始分段数,真正决定并发度的是concurrencyLevel(Java 7)或内部计算的并行度(Java 8+),但该参数仅作参考,JVM 会向上取整到最近的 2 的幂 - 避免在循环中反复调用
size():它需要遍历所有段并求和,在高并发下开销大且结果可能已过期;如需精确计数,改用mappingCount()(返回long,更准确且无锁) -
computeIfAbsent()是线程安全的“检查-创建”原子操作,比手动get()+put()更可靠,尤其适合缓存场景
什么时候不该用 ConcurrentHashMap
如果你的使用场景里写操作极少、读极多,且对内存占用敏感,CopyOnWriteArrayList 类思路的不可变 map(比如 ImmutableMap)可能更轻量;但 ConcurrentHashMap 本身并不适合做“只读快照”用途——它的迭代器是弱一致的,不能保证看到全部最新更新,也不提供冻结接口。
- 不要把它当全局配置中心:没有版本控制、变更通知或原子批量更新能力;需要这些功能应选
Curator配合 ZooKeeper 或Etcd客户端 - 避免在
compute*方法里执行耗时 I/O 或阻塞操作:会拖慢整个桶的写入,甚至引发线程饥饿 - 不要依赖
keySet()/values()返回集合的实时性:它们是弱一致视图,修改后不一定立刻反映在已有视图中
典型误用:用 putIfAbsent 替代逻辑锁却忽略返回值
putIfAbsent() 确实是原子的,但它只保证“键不存在时插入”,不保证后续逻辑执行的原子性。常见错误是写成:map.putIfAbsent(key, computeExpensiveValue());——即使 key 已存在,computeExpensiveValue() 依然会被执行。
立即学习“Java免费学习笔记(深入)”;
正确做法是用 computeIfAbsent(),它只在 key 缺失时才调用 mapping 函数:
map.computeIfAbsent(key, k -> {
// 这里只在 key 不存在时执行
return loadFromDB(k);
});
- 如果
computeIfAbsent()中抛出异常,该 key 不会被写入,且异常向外传播 - 函数内不能修改当前 map(比如再调
put()),否则可能死锁(JDK 8 中已修复多数情况,但仍建议避免) - 注意 lambda 捕获的变量必须是 effectively final;若需更新外部状态,改用
compute()并处理旧值
扩容期间的可见性与性能抖动
ConcurrentHashMap 扩容是渐进式、并发进行的:一个线程触发扩容后,其他线程在访问时也可能协助迁移桶。这意味着扩容过程不会阻塞读写,但会带来额外 CPU 和内存压力。
- 扩容时新老 table 共存,读操作可能从两个表中分别取值,所以
size()在扩容中误差更大;务必用mappingCount() - 如果写入速率持续超过扩容速度(比如大量突发写入),可能引发连续扩容,表现为 CPU 使用率突增;可通过预估容量 + 合理负载因子(默认 0.75)缓解
- Java 8 中,链表转红黑树阈值是 8,但前提是 table length ≥ 64;否则优先扩容而非树化——这点常被忽略,导致小容量 map 下仍出现长链表竞争
真正难处理的不是“怎么用”,而是“什么时候它也不够用”:比如需要跨 key 的原子事务、强一致性遍历、或带 TTL 的自动驱逐——这些得靠 Caffeine 或自研分段锁封装,而不是硬撑 ConcurrentHashMap。


















