ConcurrentHashMap是怎么实现线程安全的


这篇文章主要介绍“ConcurrentHashMap是怎么实现线程安全的”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“ConcurrentHashMap是怎么实现线程安全的”文章能帮助大家解决问题。我们知道,在日常开发中使用的 HashMap 是线程不安全的,而线程安全类 HashTable 和 Synchroni免费云主机域名zedMap 只是简单的在方法上加锁实现了线程安全,效率低下,所以在线程安全的环境下我们通常会使用 ConcurrentHashMap,那么 ConcurrentHashMap 又是如何实现线程安全的呢?针对这个问题,可以从以下几个方面来阅读源码予以解答在 JDK 1.8 中,初始化 ConcurrentHashMap 的时候这个 Node[] 数组是还未初始化的,会等到第一次 put() 方法调用时才初始化此时会有并发问题的,如果多个线程同时调用 initTable() 初始化 Node[] 数组怎么办?看看 Doug Lea 大师是如何处理的table 变量使用了 volatile 来保证每次获取到的都是最新写入的值ConcurrentHashMap 源码中 sizeCtl 变量注释如下在 ConcurrentHashMap 初始化时,初始化 sizeCtl2.1.1. 总结就算有多个线程同时进行 put 操作,在初始化 Node[] 数组时,使用了 CAS 操作来决定到底是哪个线程有资格进行初始化,其他线程只能等待。用到的并发技巧如下:volatile 修饰 sizeCtl 变量:它是一个标记位,用来告诉其他线程这个坑位有没有线程在进行初始化工作,其线程间的可见性由 volatile 保证CAS 操作:CAS 操作保证了设置 sizeCtl 标记位的原子性,保证了在多线程同时进行初始化 Node[] 数组时,只有一个线程能成功值得关注的是 tabAt(tab, i) 方法,其使用 Unsafe 类 volatile 的操作 volatile 式地查看值,保证每次获取到的值都是 最新 的虽然上面的 table 变量加了 volatile,但也只能保证其引用的可见性,并不能确保其数组中的对象是否是最新的,所以需要 Unsafe 类 volatile 式地拿到最新的 Nodeput() 方法的核心思想:由于其减小了锁的粒度,若 Hash 完美不冲突的情况下,可同时支持 n 个线程同时 put 操作,n 为 Node 数组大小,在默认大小 16 下,可以支持最大同时 16 个线程无竞争同时操作且线程安全当 Hash 冲突严重时,Node 链表越来越长,将导致严重的锁竞争,此时会进行扩容,将 Node 进行再散列,下面会介绍扩容的线程安全性。总结一下用到的并发技巧减小锁粒度:将 Node 链表的头节点作为锁,若在默认大小 16 情况下,将有 16 把锁,大大减小了锁竞争(上下文切换),就像开头所说,将串行的部分最大化缩小,在理想情况下线程的 put 操作都为并行操作。同时直接锁住头节点,保证了线程安全使用了 volatile 修饰 table 变量,并使用 Unsafe 的 getObjectVolatile() 方法拿到最新的 NodeCAS 操作:如果上述拿到的最新的 Node 为 null,则说明还没有任何线程在此 Node 位置进行插入操作,说明本次操作是第一次synchronized 同步锁:如果此时拿到的最新的 Node 不为 null,则说明已经有线程在此 Node 位置进行了插入操作,此时就产生了 hash 冲突;此时的 synchronized 同步锁就起到了关键作用,防止在多线程的情况下发生数据覆盖(线程不安全),接着在 synchronized 同步锁的管理下按照相应的规则执行操作当 hash 值相同并 key 值也相同时,则替换掉原 value否则,将数据插入链表或红黑树相应的节点对于 get 操作其实没有线程安全的问题,只有可见性的问题,只需要确保 get 的数据是线程之间可见的即可在 get 操作中除了增加了迁移的判断以外,基本与 HashMap 的 get 操作无异,这里不多赘述,值得一提的是这里使用了 tabAt() 方法 Unsafe 类 volatile 的方式去获取 Node[] 数组中的 Node,保证获得到的 Node 是最新的在扩容时,ConcurrentHashMap 支持多线程并发扩容,在扩容过程中同时支持 get 查数据,若有线程 put 数据,还会帮助一起扩容,这种无阻塞算法,将并行最大化的设计,堪称一绝2.4.1. 扩容时的 get 操作假设 Node下标为 16 的 Node 节点正在迁移扩容,突然有一个线程进来调用 get() 方法,正好 key 又散列到下标为 16 的节点,此时怎么办?在 get() 操作的源码中,会判断 Node 中的 hash 是否小于 0(eh
到这里之所以占位 Node 需要保存新 Node[] 数组的引用也是因为这个,它可以支持在迁移的过程中照样不阻塞地查找值,可谓是精妙绝伦的设计2.4.2. 多线程协助扩容在 put 操作时,假设正在迁移扩容,正好有一个线程进来,想要 put 值到迁移的 Node上,怎么办?在 put() 方法中调用了 helpTransfer() 方法此方法涉及大量复杂的位运算,这里只是简单的说几句,此时 sizeCtl 变量用来表示 ConcurrentHashMap 正在扩容,当其准备扩容时,会将 sizeCtl 设置为一个负数2.4.3. 总结ConcurrentHashMap 运用各类 CAS 操作,将扩容操作的并发性能实现最大化,在扩容过程中,就算有线程调用 get 查询方法,也可以安全的查询数据若有线程进行 put 操作,还会协助扩容利用 sizeCtl 标记位和各种 volatile 变量进行 CAS 操作达到多线程之间的通信、协助,在迁移扩容过程中只锁一个 Node 节点,即保证了线程安全,又提高了并发性能在 put 值时,发现 Node 为占位 Node(ForwardingNode)时,会协助扩容在 put 值时,检测到单链表长度大于 8 时treeifyBin() 方法会将单链表转换为红黑树,增加查找效率,但在这之前,会检查数组长度,若小于 64,则会优先做扩容操作在每次 put 值之后,都会调用 addCount() 方法,检测 Node[] 数组大小是否达到阈值关于“ConcurrentHashMap是怎么实现线程安全的”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注百云主机行业资讯频道,小编每天都会为大家更新不同的知识点。

相关推荐: react的装饰器和HOC怎么应用

本篇内容主要讲解“react的装饰器和HOC怎么应用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“react的装饰器和HOC怎么应用”吧! 简单来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件。 组件是…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 02/10 18:18
下一篇 02/10 18:23

相关推荐