ConcurrentHashMap底层原理?

本文为面试必备系列篇,不深刻叙述,具体细节可自行查询。

可能会问的问题

一、用过ConcurrentHashMap吗?
二、为何要用ConcurrentHashMap?
三、HashMap与HashTable的区别,引出ConcurrentHashMap…
四、HashMap在多线程环境下存在线程安全问题,那你通常都是怎么处理这种状况的?
五、能说一下ConcurrentHashMap是怎么实现的吗?html

为何要用ConcurrentHashMap?面试

在并发编程中使用HashMap可能会致使程序陷入死循环,而使用线程安全的HashTable效率又很是低,因此采用了ConcurrentHashMap。算法

单看这个回答,就会牵扯到「为和编发编程中使用HashMap会致使程序陷入死循环?」和「HashTable为什么效率低下?」这两个问题,具体可参考上篇 > 面试必备:HashMap底层数据结构?jdk1.8算法优化,hash冲突,扩容等问题编程

关于ConcurrentHashMap实现原理的两个参考回答,本身能够从新组织一下:数组

ConcurrentHashMap采用的是分段式锁,与之对应的就是HashTable,HashTable使用的是Synchronize关键字,是对一个大的数组加一把锁,实际上是对对象加锁,锁住的是对象总体,性能确定是比较差的,如今ConcurrentHashMap是将大数组拆分红许多的小数组,每个小数组拥有一把锁,容许多个修改操做并发进行。安全

ConcurrentHashMap采用的是分段式锁,能够理解为把一个大的Map拆封成N个小的Segment,在put数据时会根据hash<key>来肯定具体存放在哪一个Segment中,Segment内部的同步机制是基于Lock操做的,每个Segment都会分配一把锁,当线程占用锁访问其中一段数据时,其余段的数据也能被其余线程访问,也就是实现并发访问。数据结构

继续拓展,分段式锁是如何实现的?

ConcurrentHashMap在JDK1.7和JDK1.8之间是有区别的,固然,这个问题也能够这样问:多线程

能说一下ConcurrentHashMap在JDK1.7和JDK1.8中的区别吗?并发

一、JDK1.7:

HashEntry数组 + Segment数组 + Unsafe 「大量方法运用」性能

JDK1.7中数据结构是由一个Segment数组和多个HashEntry数组组成的,每个Segment元素中存储的是HashEntry数组+链表,并且每一个Segment均继承自可重入锁ReentrantLock,也就带有了锁的功能,当线程执行put的时候,只锁住对应的那个Segment 对象,对其余的 Segment 的 get put 互不干扰,这样子就提高了效率,作到了线程安全。

额外补充:咱们对 ConcurrentHashMap 最关心的地方莫过于如何解决 HashMap 在 put 时候扩容引发的不安全问题?

在 JDK1.7 中 ConcurrentHashMap 在 put 方法中进行了两次 hash 计算去定位数据的存储位置,尽量的减少哈希冲突的可能行,而后再根据 hash 值以 Unsafe 调用方式,直接获取相应的 Segment,最终将数据添加到容器中是由 segment对象的 put 方法来完成。因为 Segment 对象自己就是一把锁,因此在新增数据的时候,相应的 Segment对象块是被锁住的,其余线程并不能操做这个 Segment 对象,这样就保证了数据的安全性,在扩容时也是这样的,在 JDK1.7 中的 ConcurrentHashMap扩容只是针对 Segment 对象中的 HashEntry 数组进行扩容,仍是由于 Segment 对象是一把锁,因此在 rehash 的过程当中,其余线程没法对 segment 的 hash 表作操做,这就解决了 HashMap 中 put 数据引发的闭环问题。

二、JDK1.8:
JDK1.7:ReentrantLock+Segment+HashEntry
JDK1.8:Synchronized+CAS+Node+红黑树

JDK1.8屏蔽了JDK1.7中的Segment概念呢,而是直接使用「Node数组+链表+红黑树」的数据结构来实现,并发控制采用 「Synchronized + CAS机制」来确保安全性,为了兼容旧版本保留了Segment的定义,虽然没有任何结构上的做用。

总之JDK1.8中优化了两个部分:

放弃了 HashEntry 结构而是采用了跟 HashMap 结构很是类似的 Node 数组 + 链表(链表长度大于8时转成红黑树)的形式

Synchronize替代了ReentrantLock,咱们一直固有的思想可能以为,Synchronize是重量级锁,效率比较低,但为何要替换掉ReentrantLock呢?

一、随着JDK版本的迭代,本着对Synchronize不放弃的态度,内置的Synchronize变的愈来愈“轻”了,某些场合比使用API更加灵活。

二、加锁力度的不一样,在JDK1.7中加锁的力度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点),也就是1.8中加锁力度更低了,在粗粒度加锁中 ReentrantLock 可能经过 Condition 来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优点就没有了,因此使用内置的 Synchronize 并不比ReentrantLock效果差。

18年专科毕业后,期间一度迷茫,最近我建立了一个公众号用来记录本身的成长。

相关文章
相关标签/搜索