面试题:HashMap & ConcurrentHashMap

一、关联文章

二、Question List

  1. HashMap 的实现原理是什么,JDK1.7和JDK1.8有什么区别?
  2. HashMap 的 put 流程是什么?
  3. HashMap 的 put 方法是否支持 key/value 传 Null ?
  4. HashMap 在并发场景下是否线程安全?为什么?会产生什么问题?
  5. 如果要保证线程操作的安全,有其它方案吗?
  6. HashTable 的锁机制是什么?
  7. ConcurrentHashMap 和 HashTable 的效率哪个高,为什么?
  8. ConcurrentHashMap 的实现原理是什么?
  9. ConcurrentHashMap 在 JDK1.7 和 JDK1.8 的区别?
  10. ConcurrentHashMap 的 put 流程是什么?
  11. ConcurrentHashMap 的 get 流程是什么?
  12. ConcurrentHashMap 的 get 流程需要加锁吗,为什么?如果不需要加锁,是如何保证线程安全的?
  13. ConcurrentHashMap 的 put 方法是否支持 key/value 传 Null ?
  14. ConcurrentHashMap 的并发度是什么?
  15. ConcurrentHashMap 扩容时,如何保证可见性?
  16. ConcurrentHashMap 中修饰 Node<K,V>[] table 的 volatile 关键字可以保证 Node.value 数据的可见性吗?为什么?

三、Answer List

1. HashMap 的实现原理是什么,JDK1.7和JDK1.8有什么区别?

数据结构:
JDK1.7:数组 + 链表
JDK1.8:数组 + 链表 + 红黑树 (链表长度超过8转红黑树)

查询效率:
JDK1.7:当某一hash值碰撞频繁时,会导致链表过长,从而降低了查询效率O(n)。
JDK1.8:为了优化上述的问题,JDK1.8 引入了红黑树来提高查询效率(logn)。

2. HashMap 的 put 流程是什么?

HashMap 源码分析

3. HashMap 的 put 方法是否支持 key/value 传 Null ?

HashMap 支持 key/value 传 Null,当传入的 key 为 Null 时,会将该值保存到 table[0] 的位置。

4. HashMap 在并发场景下是否线程安全?为什么?会产生什么问题?

HashMap 是线程不安全的容器。

JDK1.7 的 HashMap 链表采用头插法添加数据。在并发场景下,同一个 table[i] 处的链表容易形成环。
JDK1.8 的 HashMap 链表采用尾插法添加数据,虽然不会产生环,但是其它的操作也会产生安全问题。

5. 如果要保证线程操作的安全,有其它方案吗?

方案1:HashMap 替换成安全容器 HashTableConcurrentHashMap
方案2:使用 Collections.synchronizedMap(Map map),代理方式。

6. HashTable 的锁机制是什么?

  1. 使用 synchronized 关键字修饰每个操作方法,即同步方法。效果与 Collections.synchronizedMap(Map map) 类似。
  2. 线程操作安全,但是锁粒度大,并发度低。

7. ConcurrentHashMap 和 HashTable 的效率哪个高,为什么?

HashTable 容器一次只支持1个线程进行操作。而 ConcurrentHashMap 容器不管是哪个版本都支持多线程操作。

8. ConcurrentHashMap 的实现原理是什么?

JDK1.7

ConcurrentHashMap 是由 Segment 数组 + HashEntry 数组 + 链表 组成。
ConcurrentHashMap 的线程安全性由 Segment 保证。Segment 继承自 ReentrantLock,所以 Segment 数组中每一个 Segment 都相当于一把锁,并发度就是 Segment 数组的长度。
Segment 数组创建好之后无法再次扩容。
Segment 内部的 HashEntry 数组可以实现扩容,所以 Segment 就类似于 HashMap。
用 volatile 修饰了 HashEntry 的数据 value 和 下一个节点 next,保证了多线程环境下数据获取时的可见性。

JDK1.8

ConcurrentHashMap 有Node 数组 + 链表 + 红黑树组成。
ConcurrentHashMap 的线程安全性由 Node数组 +CAS + Synchronized 保证。
操作粒度比JDK1.7更细,并发度更高。
用 volatile 修饰了 Node数组 和 Node 数据的 value 和 下一个节点 next,保证了多线程环境下数据获取时的可见性。

9. ConcurrentHashMap 在 JDK1.7 和 JDK1.8 的区别?

数据结构不同:

JDK1.7:Segment 数组 + HashEntry 数组 + 链表。
JDK1.8:Node 数组+链表+红黑树。

扩容:

JDK1.7:Segment 数组创建好之后无法扩容,只能扩容Segment里面的数组,所以并发度不会改变。
JDK1.8:Node是数组可以实现扩容,并发度随之提高。

操作效率不同:

并发度不同。

10. ConcurrentHashMap 的 put 流程是什么?

ConcurrentHashMap 源码分析(JDK1.7)
ConcurrentHashMap 源码分析(JDK1.8)

11. ConcurrentHashMap 的 get 流程是什么?

ConcurrentHashMap 源码分析(JDK1.7)
ConcurrentHashMap 源码分析(JDK1.8)

JDK1.7 需要计算两次hash,一次计算Segment的位置,一次计算HashEntry的位置。
JDK1.8 只需要计算一次hash。

12. ConcurrentHashMap 的 get 流程需要加锁吗,为什么?如果不需要加锁,是如何保证线程安全的?

get 操作不需要加锁。通过 volatile 语义保证了操作的安全性。

JDK1.7

虽然Segment数组没有使用 volatile 修饰,但通过 UNSAFE.getObjectVolatile() 方法保持了和 volatile 同样的语义操作。

JDK1.8

因为 Node 的元素 value 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改节点的 value 或者新增节点的时候是对线程B可见的。 这也是它比其他并发集合比如 Hashtable、用 Collections.synchronizedMap() 包装的 HashMap 效率高的原因之一。

13. ConcurrentHashMap 的 put 方法是否支持 key/value 传 Null ?

不支持key/value 传 Null。如果支持 key 为Null,则在取值时会出现二义性。

原因:

HashMap 支持 key 传 Null是因为它的操作是在单线程上进行的。当取出的 value 为 Null 时,存在key存在和不存在两种情况,此时我们可以通过 containsKey 方法来判断 key 的存在性,从而区分两种情况。但 ConcurrentHashMap 的操作是在多线程下进行的,无法区别是哪种情况(可能是在取值的过程中,其它线程删除了),因此会出现二义性。

14. ConcurrentHashMap 的并发度是什么?

JDK1.7:并发度为 Segment 的数组长度,默认值16。
JDK1.8:并发度为 Node 的数组长度,并发度会随着数组扩容而增长。

15. ConcurrentHashMap 扩容时,如何保证可见性?

JDK1.7:Segment 数组无法扩容,所以无可见性问题,HashEntry 数组通过 volatile 修饰来保证可见性。
JDK1.8:通过 volatile 修饰 Node数组来保证可见性。

16. ConcurrentHashMap 中修饰 Node<K,V>[] table 的 volatile 关键字可以保证 Node.value 数据的可见性吗?为什么?

修饰 Node<K,V>[] table 的 volatile 关键字保证的是 Node 数组扩容时,其它线程可见。
Node.value 值的可见性需要使用 volatile 关键字修饰 Node.value 来保证。

四、参考

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值