ConcurrentHashMap
是 Java 中的一个高性能线程安全的哈希表实现,随着 JDK 版本的迭代,其内部实现也经历了多次优化和改进。每个版本的改动针对不同的场景和需求进行了性能提升和问题修复。以下分别描述了 JDK 7、JDK 8 和 JDK 17 的主要设计和区别,并探讨了 JDK 17 的优化。
JDK 7 中的 ConcurrentHashMap
在 JDK 7 中,ConcurrentHashMap
使用的是 分段锁(Segment-based locking) 的设计。这种设计是通过将整个哈希表分成若干段(Segment),每段锁住部分桶来允许更高的并发度。
- 设计特点 :
-
- 哈希表被划分为多个
Segment
,每个Segment
都是一个独立的小型哈希表。 - 针对每个
Segment
使用一个独立的锁,也就是说,一个线程修改某个Segment
的数据不会影响其他线程对其他Segment
的访问。 - 每次触发写操作时,只需要对相应
Segment
加锁,而不是全表加锁。
- 哈希表被划分为多个
- 优缺点 :
-
- 在高并发下性能表现较好,读操作无需锁定,只锁定写操作。
- 并发粒度取决于
Segment
的数量(默认是 16),并发度有限。 - 容量扩展时,每个
Segment
独立扩容,操作较复杂。
JDK 8 中的 ConcurrentHashMap
JDK 8 对 ConcurrentHashMap
的实现进行了大幅改进,采用了更加细粒度的锁和无锁化设计,摒弃了 JDK 7 中的分段锁结构,转而引入基于 CAS(Compare-And-Swap)的操作和红黑树优化。
- 设计特点 :
-
- 引入了
Node
数组结构,直接取代了 Segments,并采用了与HashMap
类似的方式存储键值对。 - CAS 操作 :通过
Unsafe
类的 CAS 指令操作底层数据,避免了锁的使用。 - 红黑树优化 :当链表长度超过一定阈值(默认 8)时,将链表转换为红黑树,以避免链表过长时导致的查询性能下降。
- 扩容时使用分批迁移机制(Rehashing ),由多个线程共同完成,降低扩容引起的性能问题。
- 对
compute()
和computeIfAbsent()
等操作进行了额外的同步控制,以支持复杂操作的线程安全性。
- 引入了
- 改进效果 :
-
- 移除了分段锁的限制,并发性提高。
- 在链表出现太长时性能瓶颈显著降低。
JDK 17 中的 ConcurrentHashMap
的优化和改进
随着 JDK 的演进,ConcurrentHashMap
在 JDK 17 中进一步完善了设计,修复了一些潜在问题,同时在结构和算法上进行了优化,提升了并发性能和稳定性。
1. 性能优化
- 更好的 CAS 重试逻辑 :
-
- CAS 失败时的回退算法(退避机制)在 JDK 17 中进一步优化,以减少自旋导致的 CPU 消耗。
- 对热点桶(比如在高并发下频繁访问的区域)进行了优化,使冲突降低。
- 减少内存屏障的开销 :
-
- 在兼容 JMM(Java 内存模型)的约束下,减少了不必要的内存屏障,改善了具体操作中的指令开销。
- 改进批量操作的并发性能 :
-
- 提升了
forEach
,search
,reduce
等聚合操作的并行度和效率,尤其是在高并发场景下对大数据集的处理能力。
- 提升了
2. 锁冲突优化
- 在高并发场景中,当多个线程试图访问同一个节点时,JDK 17 中对节点锁的分配和抢占做了额外优化。例如,通过更智能的锁竞争算法来减少线程切换带来的上下文切换成本。
3. 红黑树相关修复
- 修复了一些早期版本中红黑树实现的边缘问题(例如某些极端情况下可能导致的死循环问题)。
- 优化了树结构在并发扩容和修改时的效率。
4. 线程挂起与唤醒机制改进
- 在高并发写操作下,当线程需要等待其他线程完成某个关键部分(如扩容操作)时,采用了更加轻量化的线程挂起与唤醒机制,减少了不必要的上下文切换和线程阻塞。
5. 代码质量和一致性
- 官方对代码进行了持续重构与优化,重点解决一些此前版本的边界条件、竞争状态(race condition)等潜在问题。
- 保持与其他并发集合类(例如
ConcurrentSkipListMap
)的操作逻辑风格一致。
JDK 7、8 和 17 中 ConcurrentHashMap
的主要差异总结
特性 | JDK 7 | JDK 8 | JDK 17 |
锁机制 | 使用分段锁( ) | 基于 CAS 和 锁 | 改进的 CAS,减少锁竞争 |
数据结构 |
+ 链表 |
+ 链表 + 红黑树 | 更优化的 + 链表 + 红黑树 |
扩容机制 | 每个 独立扩容 | 分批迁移完成扩容 | 改进的扩容效率,线程间协作更高效 |
高并发性能 | 易受分段数量限制 | 支持更高并发 | 性能进一步优化,高并发吞吐率提升 |
复杂操作支持(如计算) | 较为有限 | 支持 等复杂操作 | 改进 等方法的性能 |
总结
- JDK 7 采用的是分段锁模型,适合中等并发的场景。
- JDK 8 引入了 CAS 和红黑树机制,极大提升了高并发场景下的性能,并摒弃了分段锁设计,成为近代 JVM 中并发集合的基础。
- JDK 17 在 JDK 8 的基础上进一步提升了并发性能,对锁冲突、CAS 回退、扩容机制等进行了优化,并修复了红黑树实现中的一些边缘问题,适用于更高并发的场景。
通常情况下,使用 JDK 17 提供的 ConcurrentHashMap
即可获得最好的性能和健壮性。