一文带你吃透ConcurrentHashMap的实现和使用

ConcurrentHashMap 是 Java 并发包中提供的线程安全且高效的哈希表实现,适用于多线程环境。其设计结合了分段锁、CAS(Compare-And-Swap)操作和细粒度同步策略,以实现高并发性能。本文将从数据结构、并发机制、关键操作等方面展开详解。

一、数据结构

  1. 基础结构

    与 HashMap 类似,底层采用 Node 数组 + 链表/红黑树

    • Node 数组(table):存储键值对,每个数组元素称为一个"桶"。
    • 链表:哈希冲突时,同一桶内的元素形成链表。
    • 红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树,优化查询效率(退化为链表的阈值为 6)。
  2. 节点类型

    • 普通节点(Node) :存储键值对,hash 值为正数。
    • 转发节点(ForwardingNode) :扩容时标记已迁移的桶,hash 值为 MOVED(-1)。
    • 树节点(TreeBin) :红黑树的根节点,hash 值为 TREEBIN(-2)。

二、并发控制机制

  1. CAS 无锁化操作

    • 初始化数组:通过 CAS 确保只有一个线程初始化数组。
    • 插入头节点:若桶为空,使用 CAS 插入新节点,避免锁竞争。
  2. 细粒度锁(synchronized)

    • 锁定桶的头节点 :当发生哈希冲突(链表或树已存在),对头节点加 synchronized 锁,保证同一桶的操作线程安全。
    • 锁粒度小:不同桶的操作可并发执行。
  3. 多线程协作扩容

    • 触发条件 :元素数量超过 容量 × 负载因子(默认 0.75)。

    • 扩容过程

      1. 创建新数组 nextTable(容量翻倍)。
      2. 分配迁移任务:线程通过 transferIndex 领取迁移区间(一个或多个桶)。
      3. 迁移数据:将旧数组中的元素复制到新数组,遇到转发节点则跳过。
      4. 完成迁移:所有线程完成后,替换旧数组为 nextTable

三、关键操作流程

  1. put 操作

    • 步骤

      1. 计算键的哈希值(spread 方法处理哈希码)。

      2. 若数组未初始化,则初始化(CAS 控制)。

      3. 定位到桶,若桶为空则 CAS 插入新节点。

      4. 若桶非空,锁住头节点:

        • 链表:遍历链表,更新或插入节点。
        • 红黑树:通过 TreeBin 插入节点。
      5. 检查是否需要扩容。

    • 并发安全 :CAS 插入头节点 + synchronized 锁。

  2. get 操作

    • 无锁读取 :直接访问数组元素,依赖 volatile 修饰的 next 指针保证可见性。
    • 处理转发节点 :若遇到 ForwardingNode,则到新数组中查找。
  3. size 操作

    • 统计方式 :基于 baseCountCounterCell[](类似 LongAdder 的分段计数)。
    • 最终值size = baseCount + ∑CounterCell[i].value

四、特性与优化

  1. 线程安全

    • 通过 CAS 和 synchronized 实现无锁化和细粒度锁,避免全局锁竞争。
  2. 高效扩容

    • 多线程协作:多个线程可同时参与数据迁移,加快扩容速度。
    • 迁移标记 :转发节点(ForwardingNode)指示其他线程跳过已处理的桶。
  3. 迭代器弱一致性

    • 不抛出异常:迭代过程中允许其他线程修改 Map。
    • 数据可能过期:迭代器创建时捕获当前数组状态,不反映后续修改。
  4. 哈希算法优化

    • spread 方法:将哈希码高 16 位与低 16 位异或,减少冲突概率。

      arduino 复制代码
      java
      static final int spread(int h) {
          return (h ^ (h >>> 16)) & HASH_BITS;
      }

五、与 Hashtable 的对比

特性 ConcurrentHashMap Hashtable
锁机制 分段锁(桶级别) + CAS 全表锁(方法级 synchronized)
并发性能 高(多线程可操作不同桶) 低(全局锁导致串行化)
Null 支持 不允许 null 键或值 不允许 null 键或值
迭代器 弱一致性(不抛异常) 强一致性(可能抛异常)
扩容策略 多线程协作扩容 单线程扩容

六、使用场景

  1. 高并发读写:如缓存系统、实时数据处理。
  2. 替代 Hashtable:需更高并发性能的场景。
  3. 线程安全 Map:多线程环境下需保证数据一致性。

七、注意事项

  1. 哈希冲突设计 :确保键的 hashCode()equals() 正确实现。
  2. 容量规划:合理设置初始容量和负载因子,减少扩容次数。
  3. 避免死锁:尽管锁粒度小,仍需避免跨桶的复合操作导致死锁。

通过上述机制,ConcurrentHashMap 在多线程环境中实现了高性能的线程安全操作,是 Java 并发编程中不可或缺的核心类之一。

更多分享

  1. 一文带你吃透Android中常见的高效数据结构
  2. 详解:ArrayMap和SparseArray在HashMap上面的改进
  3. 详解:HashMap与TreeMap、HashTable的区别
  4. 详解:Set集合是如何保证元素不重复的
  5. 详解:LinkedHashMap的工作原理和实现
  6. 一文带你搞懂HashSet和TreeSet的区别
相关推荐
嘵奇11 分钟前
Spring Boot 跨域问题全解:原理、解决方案与最佳实践
java·spring boot·后端
黄雪超16 分钟前
JVM——方法内联之去虚化
java·开发语言·jvm
h汉堡27 分钟前
C/C++内存管理
java·c语言·开发语言·c++·学习
枣伊吕波1 小时前
第六节第二部分:抽象类的应用-模板方法设计模式
android·java·设计模式
萧然CS1 小时前
使用ADB命令操作Android的apk/aab包
android·adb
xinxiyinhe1 小时前
内存泄漏与OOM崩溃根治方案:JVM与原生内存池差异化排查手册
java·开发语言·jvm
水水沝淼㵘1 小时前
嵌入式开发学习日志(数据结构--顺序结构单链表)Day19
linux·服务器·c语言·数据结构·学习·算法·排序算法
心向阳光的天域1 小时前
黑马Java跟学.最新AI+若依框架项目开发(一)
java
what_20181 小时前
分布式链路跟踪
java·运维·分布式
oliveira-time1 小时前
ArrayList和LinkedList区别
java·开发语言