一、谈谈你理解的 HashMap,讲讲其中的 get put 过程
HashMap 底层是基于数组+链表组成的容器,key-value允许空值,不同步、线程不安全
Put过程:
-
先判断数组是否需要初始化;
-
如果key为空,则put一个null值进去;
-
根据key计算出hashcode;
-
根据hashcode定位出所在的捅;
-
如果捅是一个链表,则要判断里面的hashcode、key是否和传入的key相等,如果相等则进行覆盖,并返回原来的值;
-
如果捅是空的,说明当前位置没有数据存入,新增一个Entry对象写入当前位置;
-
当调用 addEntry 写入 Entry 时需要判断是否需要扩容。
Get过程:
-
根据key计算hashcode,定位到具体的桶;
-
判断该位置是否是链表,不是链表就根据key、key的hashcode是否相等来返回值,是链表就遍历直到key以及hashcode相等的时候就返回值;
-
啥都没取到就返回null.
二、1.8 做了什么优化?
-
优化了查询效率,TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。
-
HashEntry 修改为 Node。
-
对大链表做了优化,修改为红黑树之后查询效率直接提高到了 O(logn)。
三、HashMap是线程安全的嘛?
不是,jdk没有对HashMap做任何同步操作
不安全会导致哪些问题?
-
并发环境下容易产生死循环,发生死锁
-
如何解决?有没有线程安全的并发容器?
-
可以使用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操作:
-
首先根据key计算hashcode;
-
判断是否需要初始化;
-
通过key定位Node,判断是否为空。空:可直接写数据,CAS尝试写入,若失败,则自旋写入;
-
若当前Node位置 hashcode==MOVED==-1 ,则可扩容;
-
若都不满足则synchronized锁简单粗暴写;
-
若数量大于treeify_thresod树阈值。则转为红黑树
Get操作:
-
根据key hashcode 寻址。
-
若在桶上,直接返回,若是红黑树,就按照树的方式遍历。
-
若都不满足就用链表的方式遍历。
五、HashMap什么时候扩容
什么时候扩容:
-
当向容器添加元素(存值)的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容
-
而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
-
所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。
另外: spring 三级缓存、加载过程、bean产生销毁过程