ConcurrentHashMap 并发读写,用了什么黑科技

Photo By Instagram love_loyi

昨天的问题

ConcurrentHashMap 在我们平时编码的过程中不是很常用,但是它在 Java 基础面试中是也是一道必考题,通常会拿出来和 HashMap 做比较,所以我们必须要掌握它。那么,你知道 ConcurrentHashMap 是如何来实现一个支持并发读写的高效哈希表呢?

我的答案

ConcurrentHashMap 同 HashMap 一样,都是 Map 接口的子类。不同之处在于 HashMap 是非线程安全的,如果想要在多线程环境下使用必须对它的操作添加互斥锁。而 ConcurrentHashMap 为并发而生,可以在多线程环境下随便使用而不用开发人员去添加互斥锁。那么 ConcurrentHashMap 是如何实现线程安全的特性呢,接下来我们就从它的 put 方法说开来,对比 HashMap 的 put 操作来一步步揭开它的神秘面纱。

1.获取哈希值

同 HashMap 一样,ConcurrentHashMap 底层也是使用一个 Node 的数组来存储数据。因此第一步也是获取要存储元素的哈希值。与 HashMap 不同的是,ConcurrentHashMap 不支持 null 键和 null 值。

2.确定 hash 槽

这一步就不多说了,同 HashMap 基本一致。

3.初始化底层数组存储

拿到 hash 槽以后就是存储元素,但是首次 put 元素时候会触发初始化底层数组操作,在 HashMap 中这一步操作是很简单的,因为是单线程操作直接初始化就可以了。但是在 ConcurrentHashMap 中就需要考虑并发问题了,因为有可能有多个线程同时 put 元素。这里就用到了我们之前文章中介绍的无锁编程利器 volatile 和 CAS 原子操作。每个线程初始化数组之前都会先获取到 volatile 修饰的sizeCtl 变量,只有设置了这个变量的值才可以初始化数组,同时数组也是由 volatile 修饰的,以便修改后能被其他线程及时察觉。不知道 volatile 和 CAS 算法的同学可以参考往期的文章 ReentranLock 实现原理居然是这样?。具体初始化代码可参考 ConcurrentHashMap 的 initTable 方法。

3.插入元素

数组初始化结束后就可以开心的插入元素了,插入数组元素又分了如下 3 个情况:

3.1当前槽中没有元素

这种情况最为简单,直接调用 Unsafe 封装好的 CAS 方法插入设置元素即可,成功则罢,不成功的话会转变为下个情况。

3.2槽中已有元素

操作已有元素也有 2 个情况:

3.2.1槽中元素正在被迁移(ConcurrentHashMap 扩容操作)

如果当前槽的元素正在被扩容到新的数组当中,那么当前线程要帮忙迁移扩容。

3.2.3槽中元素处于普通状态

这个时候就是和 HashMap 类似的 put 操作,特殊之处在于 ConcurrentHashMap 需要在此处加锁,因为可能当前有多个线程都想往这个槽上添加元素。这里使用的 synchronized 锁,锁对象为当前槽的第一个元素。锁住以后的操作就和 HashMap 类似了,如果槽中依然是列表形态,那么将当前元素包装为 Node 添加到队列尾部,插入元素结束后如果发现列表元素达到了 8, 则会将列表转换为二叉树(注意:插入元素后会释放锁,若发现要转换为二叉树,则会重新进行加锁操作)。如果槽中元素已经转换为了平衡二叉树,那么将元素封装为 TreeNode 插入到树中。

4.扩容

在插入元素的末尾,ConcurrentHashMap 会计算当前容器内的元素,然后决定是否需要扩容。扩容这一部分涉及到了比较复杂的运算,后面我们会单独出一遍文章来探讨分析,敬请关注。

至此相信你已经对 ConcurrentHashMap 可以支持并发的原理有了大致的了解,它主要依靠了 volatile 和 CAS 操作,再加上 synchronized 即实现了一个支持并发的哈希表。了解了以上的内容和我们后面将要讨论的扩容话题基本可以对付面试了,当然啦 ConcurrentHashMap 的源码复杂程度远远高于 HashMap,对源码有兴趣的同学可以继续努力啃一啃,相信肯定会有更大的收获。

以上即为昨天的问题的答案,小伙伴们对这个答案是否满意呢?欢迎留言和我讨论。

又要到年末了,你是不是又悄咪咪的开始看机会啦。为了广大小伙伴能充足电量,能顺利通过 BAT 的面试官无情三连炮,我特意推出大型刷题节目。每天一道题目,第二天给答案,前一天给小伙伴们独立思考的机会。

点下“在看”,鼓励一下?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值