ConcurrentHashMap的红黑树仅是链表过长时的查询优化结构,不参与锁机制;并发控制由CAS和synchronized头节点锁实现,树化触发条件为链表≥8且数组长度≥64。

ConcurrentHashMap 的红黑树机制本身不提供“锁化”,它是一种读写优化结构,不是锁策略。真正起并发控制作用的是 CAS + synchronized 头节点锁,红黑树只是在链表过长时提升查询效率的底层数据形态。想提升高并发下的读写带宽,关键不在“怎么锁红黑树”,而在于让写操作尽可能走无锁路径、读操作完全免锁,并避免触发不必要的树化或扩容争用。
理解红黑树的真实角色:它不参与加锁决策
红黑树在 ConcurrentHashMap 中仅作为链表的替代结构存在,触发条件明确:
- 链表节点数 ≥ 8 且 当前数组长度 ≥ 64,才会将该桶(bin)的链表转为红黑树
- 当红黑树节点数 ≤ 6,会退化回链表
- 树化/退化过程本身不改变锁行为:无论桶内是链表还是红黑树,对它的写操作仍只锁定头节点(synchronized(Node)),读操作全程无锁
实战中真正影响读写带宽的三个核心点
红黑树只是性能拐点的“结果”,不是优化入口。带宽瓶颈通常来自以下环节:
- 哈希分布不均:大量 key 落入同一桶,导致单个 synchronized 锁竞争加剧,即使已树化,写吞吐仍受限于头节点锁
- 频繁扩容:put 过程中若触发 table 扩容,会启动多线程协助迁移(helpTransfer),此时大量线程忙于搬运而非业务写入,CPU 和内存带宽被严重占用
- 读多写少场景下误用 get() 配合 computeIfAbsent():后者是写操作,会加锁;若本可预热或批量初始化,却在热点路径反复调用,就人为制造锁争用
可立即落地的带宽优化建议
不依赖红黑树,而是从使用模式和参数入手:
- 初始化时显式指定初始容量与并发预期:new ConcurrentHashMap(65536, 0.75f) —— 避免早期频繁扩容;65536 是 2 的幂,也利于位运算寻址
- 确保 key 的 hashCode() 分布均匀:自定义 key 类必须重写 hashCode(),避免所有 key 返回相同值(如只返回常量)
- 写操作优先用 putIfAbsent / computeIfPresent 等条件更新方法,减少无谓的锁获取和 val 赋值开销
- 对高频读+低频写场景,考虑用 LongAdder 或 striped counter 替代 map.get(key).increment(),避免每次读都触发 volatile 读+CAS 写的组合开销
什么时候该关注红黑树?—— 只有监控信号出现时
不要主动“启用”红黑树,而应把它当作一个性能诊断信号:
- 通过 JMX 或 jcmd 查看 ConcurrentMap 的
treeifyThreshold和实际 tree bin 数量,若发现大量桶已树化但业务 QPS 未提升,说明哈希冲突根源未解(比如 key 设计缺陷) - 使用 Arthas trace ConcurrentHashMap.putVal 观察是否长期卡在 treeifyBin() 或 find() 调用上,这往往意味着某类 key 集中落桶,需针对性优化 key 生成逻辑
- 注意:JDK 17+ 引入了
TreeBin的读写分离设计(读不阻塞写),若升级到较新版本,可进一步降低树桶读写干扰

















