jdk7中concurrenthashmap按segment独立扩容:每个segment维护count和threshold,count≥threshold时触发2倍扩容,新建数组、头插法迁移、volatile更新table,互不影响。

在 JDK 7 中,ConcurrentHashMap 的每个 Segment 内部维护一个独立的 HashEntry 数组,扩容是按 Segment 粒度进行的,彼此完全隔离。也就是说:一个 Segment 扩容时,不影响其他 Segment 的结构或锁状态。
Segment 内部 HashEntry 数组的扩容触发条件
每个 Segment 维护自己的元素数量 count 和扩容阈值 threshold。当执行 put 操作导致该 Segment 的 count ≥ threshold 时,就触发该 Segment 的局部扩容。
- 初始容量由构造参数决定:若未显式指定,并发度(
concurrencyLevel)默认为 16,总初始容量默认为 16,则每个 Segment 的 HashEntry 数组初始容量为roundUpToPowerOf2(16 / 16) = 1 → 向上取整为 2 - 负载因子固定为
0.75,因此初始threshold = (int)(2 × 0.75) = 1,即插入第 1 个元素后就可能触发扩容(取决于是否发生哈希冲突及实际计数逻辑) -
threshold在每次扩容后重新计算:新阈值 = 新容量 × 0.75
扩容过程:新建数组 + 逐节点迁移 + 重哈希
扩容不是原地扩容,而是创建一个容量翻倍的新 HashEntry 数组(注意:不是 1.5 倍,资料中“HashEntry 扩容 1.5 倍”属误传;JDK 7 ConcurrentHashMap 的 HashEntry 数组扩容始终是 2 倍,与 HashMap 一致),然后将旧数组中所有非空链表头节点及其后续节点逐个迁移过去。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 新数组容量 = 原数组容量 × 2(必须是 2 的幂)
- 对每个 HashEntry 节点,重新计算其在新数组中的下标:
index = hash & (newCapacity - 1) - 迁移采用头插法(与 JDK 7 HashMap 相同),所以链表顺序会反转
- 迁移期间该 Segment 处于加锁状态(
ReentrantLock.lock()),其他线程访问该 Segment 会被阻塞,但访问其他 Segment 不受影响
为什么不是所有 Segment 同时扩容?
因为每个 Segment 是独立的锁+数据结构单元:
- 没有全局扩容协调机制,各 Segment 自行判断、自行扩容
- 不同 Segment 的负载差异大:有的可能长期空闲,有的早已填满,无需同步动作
- 这种设计正是“分段锁”高并发能力的基础——写操作只锁住当前 Segment,读操作甚至无锁(依赖 volatile 和 final 语义)
扩容后的锁与可见性保障
扩容完成后,Segment 的 table 引用被原子更新为新数组(通过 volatile 写保证可见性),同时更新 threshold 和 count。由于 HashEntry.next 和 value 都是 volatile 修饰的,且迁移过程在锁内完成,因此其他线程在后续读取时能安全看到一致的结构和最新值。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










