concurrentHashMap原理

实现原理

先来看下JDK1.7

JDK1.7 中的 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即 ConcurrentHashMap 把哈希桶数组切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。

如下图所示,首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问,实现了真正的并发访问。

Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下

 

Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。Segment 默认为 16,也就是并发度为 16。并且不能懒惰加载。

存放元素的 HashEntry,也是一个静态内部类,主要的组成如下:

 

 其中,用 volatile 修饰了 HashEntry 的数据 value 和 下一个节点 next,保证了多线程环境下数据获取时的可见性

再来看下JDK1.8

在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加细粒度的锁。

将锁的级别控制在了更细粒度的哈希桶数组元素级别,也就是说只需要锁住这个链表头节点(红黑树的根节点),就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。

小结

 Java 8 数组(Node) +( 链表 Node | 红黑树 TreeNode ) 以下数组简称(table),链表简称(bin)
初始化,使用 cas 来保证并发安全,懒惰初始化 table
树化,当 table.length < 64 时,先尝试扩容,超过 64 时,并且 bin.length > 8 时,会将链表树化,树化过程
会用 synchronized 锁住链表头
put,如果该 bin 尚未创建,只需要使用 cas 创建 bin;如果已经有了,锁住链表头进行后续 put 操作,元素
添加至 bin 的尾部
get,无锁操作仅需要保证可见性,扩容过程中 get 操作拿到的是 ForwardingNode 它会让 get 操作在新
table 进行搜索
扩容,扩容时以 bin 为单位进行,需要对 bin 进行 synchronized,但这时妙的是其它竞争线程也不是无事可
做,它们会帮助把其它 bin 进行扩容,扩容时平均只有 1/6 的节点会把复制到新 table 中
size,元素个数保存在 baseCount 中,并发时的个数变动保存在 CounterCell[] 当中。最后统计数量时累加
即可

 

1、为什么ConcurrentHashMap不允许插入null值?

在Java语言中,给ConcurrentHashMap和Hashtable这些线程安全的集合中的Key或者Value插入  null(空)  值的会报空指针异常,但是单线程操作的HashMap又允许  Key   或者  Value   插入  null(空) 值。这到底是为什么呢?

 那为什么要这么设计?

因为给ConcurrentHashMap中插入 null (空)值会存在歧义。我们可以假设ConcurrentHashMap允许插入 null(空) 值,那么,我们取值的时候会出现两种结果:

1、值没有在集合中,所以返回的结果就是 null (空);

2、值就是 null(空),所以返回的结果就是它原本的 null(空) 值。

这就产生歧义,那hashmap就不会产生歧义吗?

因为hashmap是单线程,如果取值为null,hashmap可以使用containsKey方法判断区分这个 null(空) 值到底是插入值是 null(空),还是本就没有才返回的 null(空) 值。

而concurrentHashMap是多线程,他取值为null,使用containsKey去判断的话,会被其他线程的添加null、删除null所干扰,无法判断null到底代表什么。

2、jdk8中,为什么使用synchronized替换 可重入锁 ReentrantLock?

jdk6中,对synchronized做了优化,添加了偏向锁、轻量级锁、重量级锁

3、ConcurrentHashMap 的并发度是什么?

jdk7中,segment的数量,默认是16,扩容后也是2的幂数。

jdk8中,是数组的大小

4、JDK1.7 与 JDK1.8 中ConcurrentHashMap 的区别

数据结构:取消了 Segment 分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
保证线程安全机制:JDK1.7 采用 Segment 的分段锁机制实现线程安全,其中 Segment 继承自 ReentrantLock 。JDK1.8 采用CAS+synchronized保证线程安全。
锁的粒度:JDK1.7 是对需要进行数据操作的 Segment 加锁,JDK1.8 调整为对每个数组元素加锁(Node)。
链表转化为红黑树:定位节点的 hash 算法简化会带来弊端,hash 冲突加剧,因此在链表节点数量大于 8(且数据总量大于等于 64)时,会将链表转化为红黑树进行存储。
查询时间复杂度:从 JDK1.7的遍历链表O(n), JDK1.8 变成遍历红黑树O(logN)。

5、ConcurrentHashMap 和 Hashtable 的效率哪个更高?为什么?

ConcurrentHashMap 的效率要高于 Hashtable,因为 Hashtable 给整个哈希表加了一把大锁从而实现线程安全。而ConcurrentHashMap 的锁粒度更低,在 JDK1.7 中采用分段锁实现线程安全,在 JDK1.8 中采用CAS+synchronized实现线程安全。

面试被问到 ConcurrentHashMap答不出 ,看这一篇就够了!_Java烂猪皮V的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值