Java后端面试高频问题:ConcurrentHashMap

1.ConcurrentHashMap底层实现

JDK1.7

底层数据结构:Segments数组+HashEntry数组+链表,采用分段锁保证安全性

一个ConcurrentHashMap中有一个Segments数组,一个Segments中存储一个HashEntry数组,每个HashEntry是一个链表结构的元素。

segment继承自ReentrantLock锁。

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

get()操作:

HashEntry中的value属性和next指针是用volatile修饰的,保证了可见性,所以每次获取的都是最新值,get过程不需要加锁。

1.将key传入get方法中,先根据key的hashcode的值找到对应的segment段。

2.再根据segment中的get方法再次hash,找到HashEntry数组中的位置。

3.最后在链表中根据hash值和equals方法进行查找。

ConcurrentHashMap的get操作跟HashMap类似,只是ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null。

put()操作:

1.将key传入put方法中,先根据key的hashcode的值找到对应的segment段

2.再根据segment中的put方法,加锁lock()。

3.再次hash确定存放的hashEntry数组中的位置

4.在链表中根据hash值和equals方法进行比较,如果相同就直接覆盖,如果不同就插入在链表中。


JDK1.8

底层数据结构:Node数组+链表+红黑树 采用Synchronized和CAS来保证线程安全。


get()操作:

get操作全程无锁。get操作可以无锁是由于Node元素的val和指针next是用volatile修饰的。

在多线程环境下线程A修改节点的val或者新增节点的时候是对线程B可见的。

1.计算hash值,定位到Node数组中的位置

2.如果该位置为null,则直接返回null

3.如果该位置不为null,再判断该节点是红黑树节点还是链表节点

如果是红黑树节点,使用红黑树的查找方式来进行查找

如果是链表节点,遍历链表进行查找

put()操作:

1.先判断Node数组有没有初始化,如果没有初始化先初始化initTable();

2.根据key的进行hash操作,找到Node数组中的位置,如果不存在hash冲突,即该位置是null,直接用CAS插入

3.如果存在hash冲突,就先对链表的头节点或者红黑树的头节点加synchronized锁

4.如果是链表,就遍历链表,如果key相同就执行覆盖操作,如果不同就将元素插入到链表的尾部,

并且在链表长度大于8, Node数组的长度超过64时,会将链表的转化为红黑树。

5.如果是红黑树,就按照红黑树的结构进行插入。


2.JDK1.8中为什么使用synchronized替换可重入锁ReentrantLock?

Segment继承了ReentrantLock,所以Segment是一种可重入锁。

1.Segment可重入锁锁住的是一个HashEntry数组,synchronized锁住的只是发生hash冲突的链表的头节点或红黑树的节点,提高了并发性能。

2.从JDK1.6开始,对 synchronized 锁的实现引入了大量的优化,并且 synchronized 有多种锁状态,会从偏向锁 -> 轻量级锁 -> 重量级锁一步步转换。

只要并发的线程可以在一定次数的自旋内拿到锁(偏向锁不用自旋),那么synchronized就不会升级为重量级锁,等待的线程也不会被挂起,减少了线程挂起和唤醒的切换的过程开销。

而ReentrantLock不会自旋,会直接挂起,这样一来就很容易会多出线程上下文开销的代价。

3.减少内存开销 。假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承 AQS 来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。


3.ConcurrentHashMap和Hashtable的区别?

1.底层数据结构:

JDK1.7的ConcurrentHashMap底层采用:Segments数组+HashEntry数组+链表

JDK1.8的ConcurrentHashMap底层采用:Node数据+链表+红黑树

Hashtable底层数据结构采用:数组+链表

2.实现线程安全的方式:

在JDK1.7中ConcurrentHashMap采用分段锁实现线程安全。

在JDK1.8中ConcurrentHashMap采用synchronized和CAS来实现线程安全。

Hashtable采用synchronized来实现线程安全。在方法上加synchronized同步锁。


4.HashMap与ConcurrentHashMap的区别?

HashMap是非线程安全的,这意味着不应该在多线程中对这些Map进行修改操作,否则会产生数据不 一致的问题,甚至还会因为并发插入元素而导致链表成环,这样在查找时就会发生死循环,影响到整个应用程序。

Collections工具类可以将一个Map转换成线程安全的实现,其实也就是通过一个包装类,然后把所有功能都委托给传入的Map,而包装类是基于synchronized关键字来保证线程安全的(Hashtable也是基于synchronized关键字),底层使用的是互斥锁,性能与吞吐量比较低。

ConcurrentHashMap的实现细节远没有这么简单,因此性能也要高上许多。

它没有使用一个全局锁来锁住自己,而是采用了减少锁粒度的方法,尽量减少因为竞争锁而导致的阻塞与冲突,而且ConcurrentHashMap的检索操作是不需要锁的。


5.ConcurrentHashMap是怎么分段分组的?

get操作:

Segment的get操作实现非常简单和高效,先经过一次再散列,然后使用这个散列值通过散列运算定位到 Segment,再通过散列算法定位到元素。get操作的高效之处在于整个get过程都不需要加锁,除非读到空的值才会加锁重读。原因就是将使用的共享变量定义成 volatile 类型。

put操作:

当执行put操作时,会经历两个步骤:

1. 判断是否需要扩容;

2. 定位到添加元素的位置,将其放入 HashEntry 数组中。

插入过程会进行第一次 key 的 hash 来定位 Segment 的位置,如果该 Segment 还没有初始化,即通过CAS 操作进行赋值,然后进行第二次 hash 操作,找到相应的 HashEntry 的位置,这里会利用继承过来的锁的特性,在将数据插入指定的 HashEntry 位置时(尾插***通过继承 ReentrantLock 的 tryLock() 方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该

Segment的锁,那当前线程会以自旋的方式去继续的调用 tryLock() 方法去获取锁,超过指定次数就挂起,等待唤醒。

#高频知识点汇总##Java##学习路径#
全部评论
2022届秋招Java后端企业面试真题汇总①:https://www.nowcoder.com/discuss/817566 2022届秋招Java后端企业面试真题汇总②:https://www.nowcoder.com/discuss/818250 2022届秋招Java后端企业面试真题汇总③:https://www.nowcoder.com/discuss/818255
点赞 回复 分享
发布于 2021-12-13 10:22
2022届秋招Java后端高频知识点汇总①--Java基础: https://www.nowcoder.com/discuss/819297 2022届秋招Java后端高频知识点汇总②--Java集合: https://www.nowcoder.com/discuss/819300 2022届秋招Java后端高频知识点汇总③--多线程: https://www.nowcoder.com/discuss/819302 2022届秋招Java后端高频知识点汇总④--Java中的锁: https://www.nowcoder.com/discuss/819304 2022届秋招Java后端高频知识点汇总⑤--JVM: https://www.nowcoder.com/discuss/819307 2022届秋招Java后端高频知识点汇总⑥--MySQL: https://www.nowcoder.com/discuss/819308 2022届秋招Java后端高频知识点汇总⑦--Redis: https://www.nowcoder.com/discuss/819310 2022届秋招Java后端高频知识点汇总⑧--计算机网络: https://www.nowcoder.com/discuss/819312 2022届秋招Java后端高频知识点汇总⑨--操作系统: https://www.nowcoder.com/discuss/819316 2022届秋招Java后端高频知识点汇总⑩--Spring: https://www.nowcoder.com/discuss/819319
点赞 回复 分享
发布于 2021-12-13 10:22
需要Java面试资料的私信我
点赞 回复 分享
发布于 2021-12-13 10:22
🎉恭喜牛友成功参与 【创作激励计划】高频知识点汇总专场,并通过审核! ------------------- 创作激励计划5大主题专场等你来写,最高可领取500元京东卡和500元实物奖品! 👉快来参加吧:https://www.nowcoder.com/discuss/804743
点赞 回复 分享
发布于 2021-12-13 10:54
666
点赞 回复 分享
发布于 2021-12-13 20:24
Java后端面试高频问题:HashMap的底层原理:https://www.nowcoder.com/discuss/820700 Java后端面试高频问题:ConcurrentHashMap:https://www.nowcoder.com/discuss/820701 Java后端面试高频问题:BIO、NIO、AIO的区别:https://www.nowcoder.com/discuss/820703 Java后端高频面试问题:线程池:https://www.nowcoder.com/discuss/820704 Java后端高频面试问题:AQS和CAS:https://www.nowcoder.com/discuss/820706 Java后端高频面试问题:String相关:https://www.nowcoder.com/discuss/821375 Java后端高频面试问题:ArrayList相关:https://www.nowcoder.com/discuss/821377 Java后端高频面试问题:垃圾回收机制:https://www.nowcoder.com/discuss/822354 Java后端高频面试问题:MySQL索引和事务:https://www.nowcoder.com/discuss/823047
点赞 回复 分享
发布于 2021-12-18 15:33
大佬有springcloud相关的吗
点赞 回复 分享
发布于 2022-01-06 08:58
前几个总结的还可以,后面就重复了
点赞 回复 分享
发布于 2022-02-14 21:00

相关推荐

头像
11-06 10:58
已编辑
门头沟学院 嵌入式工程师
双非25想找富婆不想打工:哦,这该死的伦敦腔,我敢打赌,你简直是个天才,如果我有offer的话,我一定用offer狠狠的打在你的脸上
点赞 评论 收藏
分享
赏个offer求你了:友塔HR还专门加我告诉我初筛不通过😂
点赞 评论 收藏
分享
11 75 评论
分享
牛客网
牛客企业服务