ConcurrentHashMap树化需同时满足链表长度≥8且表长≥64,由首个获取bin头节点锁的线程在加锁后二次校验并执行;高并发下扩容常优先介入,降低树化概率。

ConcurrentHashMap 的树化(即链表转红黑树)在并发环境下不是由单个线程“主动触发”的,而是通过多个线程协作、在扩容和插入过程中自然达成条件后完成的。关键在于:树化本身是**延迟且分段进行的**,不依赖全局锁,而是在每个 bin(桶)的头节点加 synchronized 锁的前提下,由首个到达该 bin 并发现需要树化的线程执行。
树化的两个前提条件必须同时满足
ConcurrentHashMap(JDK 8+)中,一个 bin 发生树化需同时满足:
- 链表长度 ≥ TREEIFY_THRESHOLD(默认为 8)
- table 数组长度 ≥ MIN_TREEIFY_CAPACITY(默认为 64)
注意:第二个条件常被忽略——即使某条链很长,若整个哈希表还没扩容到 64,也不会树化,而是优先触发扩容(tryPresize)。
并发下谁来执行树化?
当线程 A 尝试向某个 bin 插入新节点,发现该 bin 已是链表且长度 ≥ 8,它会先尝试获取该 bin 头节点的监视器锁(synchronized(f))。一旦获得锁,它会:
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 再次遍历确认当前链表长度是否仍 ≥ 8(防止其他线程已树化或删除)
- 检查 table.length 是否 ≥ 64;若不足,放弃树化,转而协助扩容
- 满足条件则调用
treeifyBin(tab, index),将链表重构为红黑树
其他线程在该 bin 上的写操作会被阻塞等待锁释放,读操作(如 get)不受影响(因使用 volatile 和 Unsafe 操作)。
扩容可能中断或推迟树化
在高并发写场景下,更常见的情况是:某 bin 链表刚到 7 或 8,但此时有线程触发了全局扩容(如 sizeCtl 达到阈值),那么所有线程会优先协助迁移(helpTransfer)。迁移过程中,原 bin 会被标记为 FWD 节点,后续写入直接转向新表,旧链表不再增长,树化自然不会发生。
换句话说:**高并发下,扩容往往比树化更早介入,起到“分流减压”作用,实际观察到树化的概率反而低于低并发+大数据量的场景。**
如何稳定复现树化?
若想验证树化行为(例如调试或单元测试),可人为控制条件:
- 初始化 map 时指定初始容量 ≥ 64(如
new ConcurrentHashMap(64)),避免早期扩容干扰 - 单线程顺序 put 相同 hash 值的 8 个对象(确保落在同一 bin),可稳定触发树化
- 多线程下可用
CountDownLatch协调,让多个线程集中写入同一个 bin(需自定义 key 的 hashCode 固定)
注意:不能仅靠多线程“狂 put”期望某 bin 必然树化——因为散列均匀性、扩容节奏、线程调度都会影响结果。

















