ConcurrentHashMap 在Jdk 17 不同版本中的优化和改进
ConcurrentHashMap 在Jdk 17 不同版本中的优化和改进
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 中进一步完善了设计,修复了一些潜在问题,同时在结构和算法上进行了优化,提升了并发性能和稳定性。
性能优化
- 更好的 CAS 重试逻辑:
- CAS 失败时的回退算法(退避机制)在 JDK 17 中进一步优化,以减少自旋导致的 CPU 消耗。
- 对热点桶(比如在高并发下频繁访问的区域)进行了优化,使冲突降低。
- 减少内存屏障的开销:
- 在兼容 JMM(Java 内存模型)的约束下,减少了不必要的内存屏障,改善了具体操作中的指令开销。
- 改进批量操作的并发性能:
- 提升了 forEach, search, reduce 等聚合操作的并行度和效率,尤其是在高并发场景下对大数据集的处理能力。
锁冲突优化
- 在高并发场景中,当多个线程试图访问同一个节点时,JDK 17 中对节点锁的分配和抢占做了额外优化。例如,通过更智能的锁竞争算法来减少线程切换带来的上下文切换成本。
红黑树相关修复
- 修复了一些早期版本中红黑树实现的边缘问题(例如某些极端情况下可能导致的死循环问题)。
- 优化了树结构在并发扩容和修改时的效率。
线程挂起与唤醒机制改进
- 在高并发写操作下,当线程需要等待其他线程完成某个关键部分(如扩容操作)时,采用了更加轻量化的线程挂起与唤醒机制,减少了不必要的上下文切换和线程阻塞。
代码质量和一致性
- 官方对代码进行了持续重构与优化,重点解决一些此前版本的边界条件、竞争状态(race condition)等潜在问题。
- 保持与其他并发集合类(例如 ConcurrentSkipListMap)的操作逻辑风格一致。
JDK 7、8 和 17 中 ConcurrentHashMap 的主要差异总结
特性 | JDK 7 | JDK 8 | JDK 17 |
---|---|---|---|
锁机制 | 使用分段锁(Segment) | 基于 CAS 和 synchronized 锁 | 改进的 CAS,减少锁竞争 |
数据结构 | Segment+ 链表 | Node+ 链表 + 红黑树 | 更优化的 Node+ 链表 + 红黑树 |
扩容机制 | 每个 Segment 独立扩容 | 分批迁移完成扩容 | 改进的扩容效率,线程间协作更高效 |
高并发性能 | 易受分段数量限制 | 支持更高并发 | 性能进一步优化,高并发吞吐率提升 |
复杂操作支持(如计算) | 较为有限 | 支持 compute 等复杂操作 | 改进 compute 等方法的性能 |
总结
- JDK 7 采用的是分段锁模型,适合中等并发的场景。
- JDK 8 引入了 CAS 和红黑树机制,极大提升了高并发场景下的性能,并摒弃了分段锁设计,成为近代 JVM 中并发集合的基础。
- JDK 17 在 JDK 8 的基础上进一步提升了并发性能,对锁冲突、CAS 回退、扩容机制等进行了优化,并修复了红黑树实现中的一些边缘问题,适用于更高并发的场景。
通常情况下,使用 JDK 17 提供的 ConcurrentHashMap 即可获得最好的性能和健壮性。