【面试】ConcurrentHashMap常见面试题(待完善)

这篇博客详细探讨了为什么需要ConcurrentHashMap,对比了HashMap和HashTable的不足。介绍了ConcurrentHashMap在JDK 1.7和1.8的设计与实现,包括其线程安全的实现机制、高并发访问的优化以及与HashMap、HashTable的区别。同时,博客讨论了并发容器中的其他关键概念,如快速失败与安全失败,并分析了ConcurrentHashMap为何比HashTable更高效。
摘要由CSDN通过智能技术生成

文章目录

0、考察范围

1、Java 的 HashMap 是如何实现的。按我的要求,候选人能够深入到内部算法实现,至少要把 HashMap 的内部数据结构实现和 get/put 算法给讲清楚。

2、能把装载因子(load factor)和再哈希(rehashing)給讲清楚。

3、延申:可以向 Java 集合框架方向延伸,也可以向 Java 多线程方向延伸,其中涉及的内容很多,例如 HashTable、ConcurrentHashMap,甚至 TreeMap、ConcurrentSkipList 和 ThreadLocal 等。HashMap 的基本原理并不难,但是延伸的内容如果没有实际动手实践过,你一般很难回答上来。

一、为什么有ConcurrentHashMap?

1、为什么会有ConcurrentHashMap?

HashMap线程不安全,HashTable效率低下。

追问1:为什么HashMap线程不安全?

1.7中transfer()链表使用头插法,多线程情况下,会成环;
1.8中putVal()若桶为空,多线程操作,值会出现覆盖情况。

追问2:为什么HashTable效率低下?

用synchronized锁住整个表来同步。线程竞争激烈时,一个线程访问HashTable同步方法,其他线程访问会处于阻塞或轮询状态。

二、ConcurrentHashMap是什么,具体操作内容?

1、1.8下ConcurrentHashMap 简单介绍?如何实现?(*6)

常见数据结构、put、get实现
1)sizeCtl 来控制了初始化、扩容大小,是否正在进行初始化和扩容
2)Node 继承至Entry,用于存储数据,是存储的基本单元,同时在基于Node的基础上,为了实现红黑树,扩展了TreeNode、TreeBin;TreeNode用于在红黑树存储数据,TreeBin封装了TreeNode,提供了读写锁;
3)get方法:计算hash值,如果定位到table本身,直接返回;如果不是,根据当前节点类型,分别按照链表和红黑树的方式去查找当前元素所在的位置
4)put方法:如果没有初始化,首先进行初始化;使用CAS无锁方式插入,如果发现需要扩容,首先进行扩容;如果存在hash冲突,需要挂在table节点下面,先将当前table节点加锁,链表按照尾插入方式进行插入,红黑树按照红黑树的结构进行插入,同时put在插入过程中,如果发现table里面的元素超过8个,就将链表改造成红黑树,并且还会进行元素个数的统计,并检查是否需要扩容;
5)扩容方法:1.8里面,为了提高效率,工作线程会进行并发扩容,同时为了避免多个线程有并发冲突,每个线程会进行步长的方式在节点之间来进行操作;

JDK 1.8ConcurrentHashMap 是在 JDK 1.8HashMap 上实现线程安全的。
存储结构采用 Node 数组 + 链表 + 红黑树 结构。省去了分段锁数组那一层,结构上与 HashMap 相同。
取消分段锁,通过 CAS + synchronized 来实现更细粒度的锁保护。这里的锁是指锁table的首个Node节点。在添加数据的时候,如果Node数组没有值的情况,则会使用CAS添加数据,CAS成功则添加成功,失败则进入锁代码块执行插入链表或红黑树或转红黑树操作。

2、ConcurrentHashMap中1.7和1.8的区别(*10)

JDK版本 1.7 1.8
底层实现 数组+链表 数组+链表 / 红黑树
数据结构 Segment 数组 + HashEntry 节点 Node 节点
分段锁,默认并发是16,一旦初始化,Segment 数组大小就固定,后面不能扩容 Volatile+CAS + synchronized 来保证并发安全性(*4)
put 操作 先获取锁,根据 key 的 hash 值 定位到 Segment ,再根据 key 的 hash 值 找到具体的 HashEntry ,再进行插入或覆盖,最后释放锁 根据 key 的 hash 值 定位到 Node节点,再判断首节点是否为空,空的话通过 cas 去赋值首节点 ; 首节点非空的话,会用 synchronized 去锁住首节点,并判断是是同个首节点,是的话再去操作
释放锁 需要显示调用 unlock()不需要
追问1:1.8 做了什么优化?为什么这么做?( 2-2 ……)

3、ConcurrentHashMap的get()方法

  1. 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
  2. 如果是红黑树那就按照树的方式获取值。
  3. 就不满足那就按照链表的方式遍历获取值。
    参考2
追问1:ConcurrentHashMap的get方法是否要加锁,为什么?

不要,Node的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改因为hash冲突修改结点的val或者新增节点的时候是对线程B可见的。

4、ConcurrentHashMap的put()方法

  1. 根据 key 计算出 hashcode 。
  2. 判断是否需要进行初始化。
  3. 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败 则自旋保证成功。
  4. 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
  5. 如果都不满足,则利用 synchronized 锁写入数据。
  6. 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

参考2

追问1:put()方法如何实现线程安全呢?……
追问2:Hashtable的 get、put 过程?

类似,只是没这么复杂,用synchronized修饰。

public synchronized V get
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值