目录
一、谈谈你理解的 HashMap,讲讲其中的 get put 过程。
前言
Map 这样的键值对key-value结构在软件开发中是非常经典的结构,常用于在内存中存放数据。
看了些技术文章,总结了一下HashMap和ConcurrentHashMap的核心知识点,采用提问解答的方式,相信看完后以后你会感觉清晰很多~
一、谈谈你理解的 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 用于判断是否需要将链表转换为红黑树的阈值。
- 抛弃了segment分段锁,采用CAS+Synchronized来保证并发安全性
- 数据结构上发生很大改动,HashEntry 修改为 Node
三、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 寻址。
- 若在桶上,直接返回,若是红黑树,就按照树的方式遍历。
- 若都不满足就用链表的方式遍历。