HashMap和ConcurrentHashMap 理解

一、谈谈你理解的 HashMap,讲讲其中的 get put 过程

HashMap 底层是基于数组+链表组成的容器,key-value允许空值,不同步、线程不安全

Put过程:

  1. 先判断数组是否需要初始化;

  2. 如果key为空,则put一个null值进去;

  3. 根据key计算出hashcode;

  4. 根据hashcode定位出所在的捅;

  5. 如果捅是一个链表,则要判断里面的hashcode、key是否和传入的key相等,如果相等则进行覆盖,并返回原来的值;

  6. 如果捅是空的,说明当前位置没有数据存入,新增一个Entry对象写入当前位置;

  7. 当调用 addEntry 写入 Entry 时需要判断是否需要扩容。

Get过程:

  1. 根据key计算hashcode,定位到具体的桶;

  2. 判断该位置是否是链表,不是链表就根据key、key的hashcode是否相等来返回值,是链表就遍历直到key以及hashcode相等的时候就返回值;

  3. 啥都没取到就返回null.

二、1.8 做了什么优化?

  1. 优化了查询效率,TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。

  2. HashEntry 修改为 Node。

  3. 对大链表做了优化,修改为红黑树之后查询效率直接提高到了 O(logn)。

三、HashMap是线程安全的嘛?     

不是,jdk没有对HashMap做任何同步操作

不安全会导致哪些问题?

  1. 并发环境下容易产生死循环,发生死锁

  2. 如何解决?有没有线程安全的并发容器?

  3. 可以使用Collections.synchronizedMap(Map map)方法,可以把hashmap变成一个同步的容器 (拥有锁限制的同步机制,内部加了synchronized锁同步关键字)有,ConcurrentHashMap,内部采用了ReentrantLock可重入锁,有效避免死锁

四、ConcurrentHashMap 是如何实现的?

由segment数组,HashEntry组成,仍是数组+链表,与HashMap类似,只是内部的Value以及链表都是volatile关键字修饰,保证其可见性。

ConcurrentHashMap使用了分段锁技术,segment继承于RenntrantLock可重入锁,默认16个,线程相互独立。(1.7)

1.7、1.8 实现有何不同?为什么这么做?

1.7:

Put操作:(由于volatile只能保证数据的可见性和顺序性,无法保证原子性,所以仍需加锁)

首先通过key定位到segment,然后再到具体的segment进行put,

将当前segment中的table根据key的hashcode定位到HashEntry,遍历该HashEntry,不为空判断传入的key和当前遍历的key是否相等,相等则覆盖旧的值。为空则新建一个HashEntry加入当前segment中,并判断是否需要扩容。最后解除获取的segment锁。

Get操作: 

只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。

由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁

1.8:(解决那就是查询遍历链表效率太低。)

1.8 抛弃了segment分段锁,取消了 ReentrantLock 改为了synchronized,采用CAS+synchronized保证并发安全性。

将Entry改为Node,val和next用volatile修饰。

采用红黑树之后可以保证查询效率

CAS:CompareAndSwag比较交换,实现多线程同步的原子指令

Put操作:

  1. 首先根据key计算hashcode;

  2. 判断是否需要初始化;

  3. 通过key定位Node,判断是否为空。空:可直接写数据,CAS尝试写入,若失败,则自旋写入;

  4. 若当前Node位置 hashcode==MOVED==-1 ,则可扩容;

  5. 若都不满足则synchronized锁简单粗暴写;

  6. 若数量大于treeify_thresod树阈值。则转为红黑树

Get操作:

  1. 根据key hashcode 寻址。

  2. 若在桶上,直接返回,若是红黑树,就按照树的方式遍历。

  3. 若都不满足就用链表的方式遍历。

五、HashMap什么时候扩容

什么时候扩容:

  • 当向容器添加元素(存值)的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容

  • 而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。 

  • 所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。

另外: spring 三级缓存、加载过程、bean产生销毁过程

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值