concurrenthashmap 通过锁分段细粒度化提升并发写吞吐量,jdk 7 用 segment 分段锁,jdk 8+ 改为桶级 synchronized 锁,配合 cas 和红黑树优化,关键在锁位置、大小与时机,并需合理设置 initialcapacity 等参数。
concurrenthashmap 的锁分段细粒度化,本质是通过缩小加锁范围、减少线程争用,来提升多线程并发写操作的吞吐量。关键不在于“有没有锁”,而在于“锁在哪”“锁多大”“什么时候锁”。不同 jdk 版本实现路径不同,但目标一致:让尽可能多的写操作互不干扰。
理解锁粒度演进的关键差异
早期(JDK 7)用 Segment 分段锁,把整个表拆成默认 16 个段,每个段一把 ReentrantLock;JDK 8 及以后彻底去掉 Segment,改用“桶级锁”——即只对哈希数组中某个下标位置(bucket)的头节点加 synchronized 锁。这意味着:
- 16 个 Segment 最多支持约 16 路并发写,但若所有 key 哈希后落到同一段,仍会串行
- 而桶锁理论上支持与数组长度相当的并发写(如初始容量 16,最多 16 个线程写不同桶),且扩容后桶数增加,可扩展性更强
- 桶锁配合 CAS,空桶插入完全无锁,冲突时才加锁,进一步降低同步开销
合理设置初始参数以匹配实际并发压力
ConcurrentHashMap 的并发能力不是固定值,它依赖初始化配置与运行时数据分布。重点调优两个参数:
- initialCapacity:影响底层 table 数组初始长度。设得太小会导致频繁扩容和链表变长,增加桶内竞争;建议按预估总键数 ÷ 0.75(默认 loadFactor)向上取整
- concurrencyLevel(仅 JDK 7 有效):用于估算 Segment 数量,JDK 8+ 已忽略该参数,但部分旧代码仍传入——它不再控制锁数量,仅作为 table 初始容量的参考依据之一
例如,预计写入 10 万条数据,可设 initialCapacity = 131072(2¹⁷),避免早期扩容带来的重哈希与锁阻塞。
避免人为制造锁竞争的常见误用
即使底层锁再细,不当使用仍会让多个线程挤进同一个桶:
- key 的 hashCode() 实现不合理(如长期返回固定值或低熵值),导致大量 key 落入同一桶
- 大量使用相同前缀的字符串 key(未重写 hashCode),易引发哈希碰撞
- 在高并发场景下频繁调用 computeIfAbsent 或 merge 等复合操作,这些方法内部会对桶加锁并执行函数体,若函数体耗时长,会显著延长锁持有时间
建议:自定义 key 类务必重写高效、均匀的 hashCode() 和 equals();对复杂逻辑,优先考虑先 get 再显式 put,而非依赖原子复合方法。
利用红黑树优化长链桶的写性能
JDK 8+ 在单个桶内链表长度 ≥ 8 且 table 长度 ≥ 64 时,自动将链表转为红黑树。这不仅加快读取(O(log n) 替代 O(n)),也间接提升写吞吐:
- 树化后,put 操作的遍历与插入更稳定,避免链表过长导致的锁持有时间不可控
- 红黑树的结构使多线程对同一桶的不同子节点修改(如插入不同 key)更少触发重平衡冲突
- 注意:treeify 相对耗时,应通过合理 initialCapacity + 均匀哈希尽量减少触发频率











