ConcurrentHashMap
1. ConcurrentHashMap的数据结构
数组 + 链表, 采用了分段锁的实现机制
2. ConcurrentHashMap初始化
首先会创建segment数组,长度为默认(16)或传入的并发值的大于等于的2的次方数,不可扩容
初始化(s0)segment对象并保存,并初始化他的属性,创建一个entry数组,entry数组初始化长度为 (容器长度 / segment数组长度)
大于等于2的次方,在下一次进行添加数据的时候,会直接取这个s0对象
这里初始化了他的segment[0]
3. ConcurrentHashMap如何存取数据的
1.7:
put():
1.先计算出key值的hash值,然后通过hash值(对segment[]进行取余)找到数组中对应的segment对象
2.尝试获取锁,失败则自旋保证成功。
3.获取锁,然后通过计算出的hash值(对hashentry[]进行取余)找出对应的entry对象
遍历链表中,查找有没有相同key值对象
有: 旧值覆盖新值
没有: 添加到链表中(1.7/头部 | 1.8/尾部)
get():
1.通过key值的hash值定位到对应segment对象,再通过hash值定位到具体的entry对象
2.遍历链表,通过equals取出数据
3.由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的
保证了内存可见性,所以每次获取时都是最新值
4.整个过程不需要加锁
1.8:
put():
1.根据 key 计算出 hashcode
2.判断是否需要进行初始化。
3.即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据
4.利用 CAS 尝试写入,失败则自旋保证成功。如果都不满足,则利用 synchronized 锁写入数据
5.如果数量大于 树形化阈值 则要转换为红黑树。
get():
1.根据计算出来的 hashcode 寻找segment,如果就在桶上那么直接返回值。
2.如果是红黑树那就按照树的方式获取值。
3.就不满足那就按照链表的方式遍历获取值。
4. 1.7、1.8 实现有何不同?为什么这么做?
线程同上.
数据结构:
1.7: 数组 + 链表
1.8:
1.7的底层还是链表,在查询数据的时候需要遍历,导致效率很低
1.8和HashMap比较像,也引入了红黑树,把值和next采用了volatile去修饰,保证了可见性
5. volatile的特性是啥?
volatile是Java提供的一种轻量级的同步机制:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值
这新值对其他线程来说是立即可见的。(实现可见性)
2.禁止进行指令重排序。(实现有序性)
3.volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
6. ConcurrentHashMap 线程安全怎么做的?
1.7:
采用了分段锁的机制,当一个线程占用锁时,不会影响到其他的Segment对象
1.8:
抛弃了原来的分段锁,采用了 CAS 和 synchronized 来保证并发的安全
7. 不安全会导致的问题
8. CAS是啥?ABA是啥?场景有哪些,怎么解决?
CAS:
是乐观锁的一种实现方式,是一种轻量级锁
线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,
若未被其他线程修改则写回,若已被修改,则重新执行读取流程
ABA:
当线程对值的修改过程中,另一个线程也对这个值进行了修改,并把它改为了原来的值
此时,这个值已经不是和原的值了
解决方案:
版本号 / 时间戳
9. synchronized锁升级策略
1.先使用 偏向锁 优先同一线程然后再次获取锁
2.如果失败,就升级为 CAS 轻量级锁,失败就会短暂自旋,防止线程被系统挂起。
3.最后如果以上都失败就升级为重量级锁。