Loading

一些ConcurrentHashMap面试题

下面是我收集的一些面试题:

JDK1.8中的ConcurrentHashMap是如何保证线程安全的?

## [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5Li8ZPS-1644594019827)(C:\Users\14470\Desktop\java笔记\java JUC\ConcurrentHashMap刨析.assets\watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6dV9zZXU=,size_16,color_FFFFFF,t_70-16444002074692.png)]
模板2:

  1. 储存Map数据的数组时被volatile关键字修饰,一旦被修改,其他线程就可见修改。因为是数组存储,所以只有改变数组内存值是才会触发volatile的可见性
  2. 如果put操作时hash计算出的槽点内没有值,采用自旋+CAS保证put一定成功,且不会覆盖其他线程put的值
  3. 如果put操作时节点正在扩容,即发现槽点为转移节点,会等待扩容完成后再进行put操作,保证扩容时老数组不会变化
  4. 对槽点进行操作时会锁住槽点,保证只有当前线程能对槽点上的链表或红黑树进行操作
  5. 红黑树旋转时会锁住根节点,保证旋转时线程安全

2.JDK7和JDK8中的ConcurrentHashMap不同点。

## [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEX4gUyQ-1644594019828)(C:\Users\14470\Desktop\java笔记\java JUC\ConcurrentHashMap刨析.assets\watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6dV9zZXU=,size_16,color_FFFFFF,t_70.png)]

3.扩容期间在未迁移到的hash桶插入数据会发生什么?

答:只要插入的位置扩容线程还未迁移到,就可以插入,当迁移到该插入的位置时,就会阻塞等待插入操作完成再继续迁移 。

4.1 正在迁移的hash桶遇到 get 操作会发生什么?

答:在扩容过程期间形成的 hn 和 ln链 是使用的类似于复制引用的方式,也就是说 ln 和 hn 链是复制出来的,而非原来的链表迁移过去的,所以原来 hash 桶上的链表并没有受到影响,因此如果当前节点有数据,还没迁移完成,此时不影响读,能够正常进行。

如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时get线程会帮助扩容。

4.2 正在迁移的hash桶遇到 put/remove 操作会发生什么?

如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时写线程会帮助扩容,如果扩容没有完成,当前链表的头节点会被锁住,所以写线程会被阻塞,直到扩容完成。

5.如果 lastRun 节点正好在一条全部都为高位或者全部都为低位的链表上,会不会形成死循环?

答:在数组长度为64之前会导致一直扩容,但是到了64或者以上后就会转换为红黑树,因此不会一直死循环 。

6.扩容后 ln 和 hn 链不用经过 hash 取模运算,分别被直接放置在新数组的 i 和 n + i 的位置上,那么如何保证这种方式依旧可以用过 h & (n - 1) 正确算出 hash 桶的位置?

答:如果 fh & n-1 = i ,那么扩容之后的 hash 计算方法应该是 fh & 2n-1 。 因为 n 是 2 的幂次方数,所以 如果 n=16, n-1 就是 1111(二进制), 那么 2n-1 就是 11111 (二进制) 。 其实 fh & 2n-1 和 fh & n-1 的值区别就在于多出来的那个 1 => fh & (10000) 这个就是两个 hash 的区别所在 。而 10000 就是 n 。所以说 如果 fh 的第五 bit 不是 1 的话 fh & n = 0 => fh & 2n-1 == fh & n-1 = i 。 如果第5位是 1 的话 。fh & n = n => fh & 2n-1 = i+n 。

7.并发情况下,各线程中的数据可能不是最新的,那为什么 get 方法不需要加锁?

答:get操作全程不需要加锁是因为Node的成员val是用volatile修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。。

8.1 ConcurrentHashMap 和 Hashtable 的区别?

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
底层数据结构
JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable是采用 数组+链表 的形式。
实现线程安全的方式(重要):

① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。

② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

8.2 ConcurrentHashMap 和 HashMap 的相同点和不同点

相同之处:

都是数组 +链表+红黑树的数据结构(JDK8之后),所以基本操作的思想一致
都实现了Map接口,继承了AbstractMap 操作类,所以方法大都相似,可以相互切换
不同之处:
ConcurrentHashMap 是线程安全的,多线程环境下,无需加锁直接使用
ConcurrentHashMap 多了转移节点,主要用户保证扩容时的线程安全

9.扩容过程中,读访问能否访问的到数据?怎么实现的?

可以的。当数组在扩容的时候,会对当前操作节点进行判断,如果当前节点还没有被设置成fwd节点,那就可以进行读写操作,如果该节点已经被处理了,那么当前线程也会加入到扩容的操作中去。

10.为什么超过冲突超过8才将链表转为红黑树而不直接用红黑树?

默认使用链表, 链表占用的内存更小
正常情况下,想要达到冲突为8的几率非常小,如果真的发生了转为红黑树可以保证极端情况下的效率

11. ConcurrentHashMap 和HashMap的扩容有什么不同?

HashMap的扩容是创建一个新数组,将值直接放入新数组中,JDK7采用头链接法,会出现死循环,JDK8采用尾链接法,不会造成死循环
ConcurrentHashMap 扩容是从数组队尾开始拷贝,拷贝槽点时会锁住槽点,拷贝完成后将槽点设置为转移节点。所以槽点拷贝完成后将新数组赋值给容器

12. ConcurrentHashMap 是如何发现当前槽点正在扩容的?

ConcurrentHashMap 新增了一个节点类型,叫做转移节点,当我们发现当前槽点是转移节点时(转移节点的 hash 值是 -1),即表示 Map 正在进行扩容.

13.描述一下 CAS 算法在 ConcurrentHashMap 中的应用

  • CAS是一种乐观锁,在执行操作时会判断内存中的值是否和准备修改前获取的值相同,如果相同,把新值赋值给对象,否则赋值失败,整个过程都是原子性操作,无线程安全问题
  • ConcurrentHashMap 的put操作是结合自旋用到了CAS,如果hash计算出的位置的槽点值为空,就采用CAS+自旋进行赋值,如果赋值是检查值为空,就赋值,如果不为空说明有其他线程先赋值了,放弃本次操作,进入下一轮循环

1、ConcurrentHashMap有哪些构造函数?

一共有五个,

2、ConcurrentHashMap使用什么技术来保证线程安全?

jdk1.7:Segment+HashEntry来进行实现的;

jdk1.8:放弃了Segment臃肿的设计,采用Node+CAS+Synchronized来保证线程安全;

3、ConcurrentHashMap的get方法是否要加锁,为什么?

不需要,get方法采用了unsafe方法,来保证线程安全。

4、ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?

弱一致性,hashmap强一直性。

ConcurrentHashMap可以支持在迭代过程中,向map添加新元素,而HashMap则抛出了ConcurrentModificationException,

因为HashMap包含一个修改计数器,当你调用他的next()方法来获取下一个元素时,迭代器将会用到这个计数器。

5、ConcurrentHashMap1.7和1.8的区别:

jdk1.8的实现降低锁的粒度,jdk1.7锁的粒度是基于Segment的,包含多个HashEntry,而jdk1.8锁的粒度就是Node

数据结构:jdk1.7 Segment+HashEntry;jdk1.8 数组+链表+红黑树+CAS+synchronized
6、

posted @ 2022-02-12 19:13  程序员小小宇  阅读(762)  评论(0编辑  收藏  举报