文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java集合ConcurrentHashMap详解

2023-01-15 09:00

关注

介绍 ConcurrentHashMap

技术是为了解决问题而生的,ConcurrentHashMap 解决了多个线程同时操作一个 HashMap 时,可能出现的内部问题。当多个线程同时操作一个 HashMap 时,有可能会出现多线程同时修改一个共享变量(HashMap 类的成员变量),导致数据被覆盖,产生意想不到的错误。

ConcurrentHashMap 内部使用了锁和各种数据结构来保证访问 Map 是线程安全的。

ConcurrentHashMap 和 HashMap 底层的数组、链表结构几乎相同,底层对数据结构的操作思路是相同的。ConcurrentHashMap 除了数组 + 链表 + 红黑树的基本结构外,新增了转移节点结构(ForwardingNode)。

介绍转移节点(ForwardingNode)

转移节点是 ForwardingNode 结构, ForwardingNode 继承了 Node。ForwardingNode 节点的 hash 值固定为 -1。ForwardingNode 比 Node 多了一个 nextTable 成员变量,nextTable 成员变量的类型是 Node 数组。nextTable 成员变量是扩容之后的新数组。

如果数组在索引 i 上的结构是 ForwardingNode,那么表示这个哈希桶内的全部节点都已经转移到扩容之后的新数组,旧的哈希桶内的数据不能发生改变。转移节点(ForwardingNode)是为了保证 ConcurrentHashMap 扩容时的线程安全。保证了当一个哈希桶内的全部节点都已经转移到扩容之后的新数组后、扩容操作完成之前,旧的哈希桶内的数据不发生变化。

当一个哈希桶内的全部节点都已经转移到扩容之后的新数组后、扩容操作完成之前,如果有其他的线程执行 put 操作,需要将新增的节点 put 到旧的哈希桶内,那么这个线程会调用 helpTransfer() 方法帮助扩容。扩容完成之后,这个线程再将要新增的节点 put 到新的哈希桶内。

ConcurrentHashMap 的新增操作

ConcurrentHashMap 在 put 方法上对数据结构的操作思路和 HashMap 相同,但 ConcurrentHashMap 的 put() 方法写了很多保障线程安全的代码。当调用 ConcurrentHashMap 的 put() 方法时,put() 方法的处理逻辑如下:

当调用 CHM 的 put() 方法时,如果 CHM 中已经存在要新增的 key,并且方法的入参 onlyIfAbsent 为 false,则替换旧值,并返回旧值。

ConcurrentHashMap 的扩容机制

ConcurrentHashMap 的扩容时机和 HashMap 相同,都是在 put() 方法的最后一步检查是否需要扩容。ConcurrentHashMap 扩容的方法叫做 transfer(),从 put() 方法的 addCount() 方法进去,就能找到 transfer() 方法。

如果 ConcurrentHashMap 中元素的数量超过了扩容的阈值(sizeCtl),那么它会调用 transfer() 方法执行扩容操作。ConcurrentHashMap 的扩容机制是扩容为原来容量的 2 倍。ConcurrentHashMap 扩容的处理逻辑和 HashMap 完全不同。

ConcurrentHashMap 扩容的大体思路如下:扩容需要把旧数组上的全部节点转移到扩容之后的新数组上,节点的转移是从数组的最后一个索引位置开始,一个索引一个索引进行的。每个线程一轮处理有限个数的哈希桶。当旧数组上的全部节点转移到扩容之后的新数组后,ConcurrentHashMap 的 table 成员变量指向扩容之后的新数组,扩容操作完成。transfer() 方法的处理逻辑如下:

介绍低位哈希桶、高位哈希桶:如果 ConcurrentHashMap 当前的数组长度为 n 时,一个节点的 key 对应的哈希桶索引为 i。那么 ConcurrentHashMap 扩容之后数组长度为 2n 时,这个节点的 key 对应的低位哈希桶的索引为 i,对应的高位哈希桶的索引为 i + n。

ConcurrentHashMap 支持多线程扩容:

ConcurrentHashMap 的查找操作

当调用 ConcurrentHashMap 的 get() 方法时,get() 方法的处理逻辑如下:

ConcurrentHashMap 的容量大小问题

ConcurrentHashMap 的数组长度总是为 2 的幂次方。不论传入的初始容量是否为 2 的幂次方,最终都会转化为 2 的幂次方。

ConcurrentHashMap 中根据 key 计算出 hash 值,然后根据计算出的 hash 值计算出 key 对应的数组索引 i:

ConcurrentHashMap 的数组长度总是为 2 的幂次方设计的非常巧妙:

ConcurrentHashMap 的计数

当调用 ConcurrentHashMap 的 put() 方法时,put() 方法的最后一步是调用 addCount() 方法。

addCount() 方法的任务是:增加 ConcurrentHashMap 中元素的计数值。如果元素的数量超过了 ConcurrentHashMap 扩容的阈值(sizeCtl),那么就会调用 transfer() 方法执行扩容操作。如果此时有其他的线程已经在执行扩容操作,那么当前线程就协助扩容。

ConcurrentHashMap 采用了一些数据结构和手段来支持高效的并发计数。ConcurrentHashMap 使用 long 类型的 baseCount 成员变量和 CounterCell 类型的 counterCells 数组来支持高效的并发计数。

ConcurrentHashMap 的计数将线程竞争分散到 counterCells 数组的每一个元素,提高了并发计数的性能。

private transient volatile long baseCount;

// 如果 counterCells 数组不为空,则数组的长度为 2 的幂次方。
private transient volatile CounterCell[] counterCells;

@sun.misc.Contended static final class CounterCell {
    volatile long value;
    CounterCell(long x) { value = x; }
}

到此这篇关于Java集合ConcurrentHashMap详解的文章就介绍到这了,更多相关Java集合ConcurrentHashMap内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯