首页 / 资讯中心 / 文章详情

解析ConcurrentHashMap:分段锁到synchronized+红黑树

解析ConcurrentHashMap:分段锁到synchronized+红黑树 ★ FEATURED ARTICLE
(❁´◡❁)您的点赞➕评论➕收藏⭐➕关注是作者创作的最大动力 支持我点赞收藏⭐️留言关注欢迎留言讨论我们常常在当下感到时间慢觉得未来遥远但一旦回头看时间已经悄然流逝。对于未来尽管如此也应该保持一种从容的态度相信未来仍有许多可能性等待着我们。JDK8 ConcurrentHashMap深度解析从分段锁到synchronized红黑树的进化之路引言并发容器的演进在多线程并发编程中ConcurrentHashMap无疑是最重要、最常用的并发容器之一。从JDK5引入之初它就以其高效的并发性能赢得了开发者的青睐。然而随着Java版本的演进ConcurrentHashMap的实现经历了深刻的变革。特别是JDK8对其进行的重大重构不仅改变了底层的数据结构更彻底革新了同步机制。本文将深入剖析JDK8中ConcurrentHashMap的实现原理揭示其为何摒弃了原有的分段锁设计转而采用更细粒度的同步策略。(免费无删减无套路)java swing管理系统源码 程序 代码 图形界面11套」链接https://pan.quark.cn/s/784a0d377810提取码见文章末尾(免费无删减无套路) Python源代码开发文档说明23套」链接https://pan.quark.cn/s/1d351abbd11c提取码见文章末尾(免费无删减无套路)计算机专业精选源码论文26套」链接https://pan.quark.cn/s/8682a41d0097提取码见文章末尾(免费无删减无套路)Java web项目源码整合开发ssm30套链接https://pan.quark.cn/s/1c6e0826cbfd提取码见文章末尾(免费无删减无套路)「在线考试系统源码含搭建教程」链接https://pan.quark.cn/s/96c4f00fdb43提取码见文章末尾一、JDK7 ConcurrentHashMap的局限分段锁的痛点1.1 分段锁的设计思想在JDK7及之前ConcurrentHashMap采用了分段锁Segment的设计。整个Map被划分为多个Segment默认为16个每个Segment本质上是一个独立的HashMap拥有自己的锁。这种设计允许多个线程同时访问不同的Segment从而提高了并发度。// JDK7中的Segment结构 static final class SegmentK,V extends ReentrantLock implements Serializable { transient volatile HashEntryK,V[] table; // 其他字段和方法... }1.2 分段锁的局限性虽然分段锁在当时是一种创新的设计但随着硬件的发展和并发场景的变化其局限性逐渐显现锁粒度仍然较粗虽然比Hashtable的全局锁细但每个Segment锁仍然保护着多个桶bucket内存开销较大每个Segment都继承自ReentrantLock包含了复杂的锁状态管理机制并发度受限于Segment数量默认16个Segment意味着最多支持16个线程完全并发查询性能不够优化链表过长时查询时间复杂度退化为O(n)二、JDK8 ConcurrentHashMap的革命性重构2.1 全新的数据结构设计JDK8彻底摒弃了Segment分段锁的设计回归到与HashMap类似的结构但进行了并发优化Node数组table ↓ 每个桶bucket独立 ↓ 链表 → 红黑树条件触发核心数据结构NodeK,V[] table核心的哈希表数组Node节点基本的存储单元包含key、value、hash和next指针TreeNode节点红黑树的节点继承自NodeForwardingNode扩容时的特殊节点2.2 细粒度的同步机制2.2.1 从ReentrantLock到synchronizedJDK8最显著的改变之一是使用synchronized替代了ReentrantLock来同步每个桶的头节点。为什么选择synchronized性能优化经过多年的JVM优化synchronized的性能已经与ReentrantLock相当甚至更好内存开销小synchronized是JVM内置的锁机制不需要额外的内存存储锁状态优化潜力大JVM可以对synchronized进行深度优化如锁消除、锁粗化、偏向锁、轻量级锁等代码简洁减少了显式锁管理的复杂性// JDK8中的putVal方法片段简化版 final V putVal(K key, V value, boolean onlyIfAbsent) { // ... 前置检查 synchronized (f) { // f是桶的头节点 // 插入或更新逻辑 } // ... 后置处理 }2.2.2 CAS操作的广泛应用除了synchronizedJDK8大量使用了CASCompare-And-Swap操作来处理无竞争的并发更新初始化table扩容时的协助迁移计数器更新sizeCtl节点插入前的空桶检查CAS操作通过sun.misc.Unsafe类实现提供了硬件级别的原子性保证。2.3 红黑树的引入解决哈希冲突的终极武器2.3.1 链表到红黑树的转换条件JDK8引入了重要的优化当链表长度过长时会将其转换为红黑树static final int TREEIFY_THRESHOLD 8; // 链表转树的阈值 static final int UNTREEIFY_THRESHOLD 6; // 树转链表的阈值 static final int MIN_TREEIFY_CAPACITY 64; // 可树化的最小表容量转换逻辑当链表长度 ≥ 8且数组长度 ≥ 64时链表转换为红黑树当树节点数 ≤ 6时红黑树退化为链表2.3.2 红黑树的优势红黑树是一种自平衡的二叉查找树具有以下特性最长路径不超过最短路径的两倍保证基本平衡查找、插入、删除的时间复杂度都是O(log n)避免了极端情况下链表过长导致的性能退化实际场景当大量元素哈希到同一个桶时可能是哈希函数问题或恶意攻击红黑树能保证O(log n)的查询效率而链表会退化为O(n)。三、核心操作原理深度剖析3.1 put操作的并发安全实现put操作是ConcurrentHashMap最复杂的操作之一它需要处理多种并发场景// put操作的主要步骤 1. 计算key的hash值spread方法进行再哈希 2. 如果table为空则初始化initTable 3. 定位到具体的桶 4. 如果桶为空CAS插入新节点 5. 如果桶不为空synchronized锁住头节点 6. 遍历链表或树查找key是否存在 7. 存在则更新不存在则插入 8. 检查是否需要树化 9. 更新size通过addCount关键优化点CAS失败重试初始化、空桶插入等操作通过CAS循环实现无锁化锁细化只锁住单个桶的头节点不影响其他桶的操作扩容协作多个线程可以协作完成扩容操作3.2 get操作的无锁优化get操作在JDK8中是完全无锁的这得益于以下设计// get操作的关键特性 1. 不需要加锁直接读取 2. 使用volatile保证内存可见性 3. 遇到ForwardingNode时转发到新表查询 4. 红黑树查找同样无锁为什么get可以不加锁Node的val和next都声明为volatile红黑树的查找是只读操作扩容时使用ForwardingNode保证正确性3.3 扩容机制多线程协作的典范JDK8的扩容机制是其并发设计的精华所在// 扩容的主要步骤 1. 创建新表2倍大小 2. 分配扩容任务stride 3. 线程协作迁移节点 4. 设置ForwardingNode标记已迁移的桶 5. 迁移完成使用新表迁移策略每个线程负责一个区间stride的迁移迁移时对原桶加锁保证迁移的原子性使用ForwardingNode标记已迁移的桶四、性能对比与实战建议4.1 JDK7 vs JDK8性能对比特性JDK7 ConcurrentHashMapJDK8 ConcurrentHashMap锁粒度Segment级别较粗桶级别更细锁类型ReentrantLocksynchronized数据结构数组链表数组链表红黑树最大并发度Segment数量默认16桶数量理论上无限制内存开销较大每个Segment一个锁较小极端情况性能链表可能很长查询O(n)转换为红黑树查询O(log n)4.2 实战使用建议选择合适的初始容量// 预估并发线程数和数据量设置合理的初始容量 int initialCapacity 64; // 根据实际情况调整 ConcurrentHashMapString, Object map new ConcurrentHashMap(initialCapacity);合理设置并发级别// JDK8中concurrencyLevel参数仅用于兼容性实际作用有限 // 推荐使用初始容量和负载因子来控制避免热点桶确保key的hashCode分布均匀自定义对象实现良好的hashCode方法正确使用原子操作// 使用computeIfAbsent等原子方法 map.computeIfAbsent(key, k - createValue(k));五、总结与展望JDK8对ConcurrentHashMap的重构是一次深刻的进化它体现了Java并发编程理念的变迁从粗粒度到细粒度锁粒度细化到每个桶提高了并发性能从复杂到简洁synchronized替代ReentrantLock简化了实现从单一到混合链表红黑树的混合结构兼顾了普通情况和极端情况从独占到协作多线程协作扩容提高了系统整体吞吐量这些改进使得ConcurrentHashMap在高并发场景下表现更加出色能够更好地适应现代多核处理器的架构特点。作为Java开发者深入理解这些原理不仅有助于我们更好地使用这个工具更能提升我们对并发编程本质的理解。随着Java版本的不断演进ConcurrentHashMap仍在持续优化。在未来的JDK版本中我们可能会看到更多基于硬件特性的优化如更细粒度的内存访问控制、更好的缓存友好性设计等。但无论如何变化对并发安全、性能、可扩展性的追求将始终是ConcurrentHashMap设计的核心目标。(❁´◡❁)您的点赞➕评论➕收藏⭐➕关注是作者创作的最大动力 支持我点赞收藏⭐️留言关注欢迎留言讨论
阅读完成 · 觉得有帮助?
咨询建站