一、ConcurrentHashMap 1.7
1、数据结构:
Segment数组 + 多个HashEntry组成、Segment数组又是由多个Segment元素组成,每个Segment元素又是由一个HashEntry数组和链表组成;
2、put操作
- 第一次计算key的hash,找到Segment元素的位置;
- 判断当前Segment元素是否初始化,若没有初始化,则通过CAS进行初始化;
- 第二次计算key的hash,找到HashEntry数组的位置;
- 由于Segment继承了ReentrantLock锁,所以TryLock() 尝试获取锁,如果锁过去成功,将数据插入到HashEntry位置,如果遇到Hash冲突,则插入到链表的末端;如果锁被其他线程获取,那么就会以自旋的方式重新获取锁,超过指定的次数之后还获取不到的话,就会挂起,等待唤醒;
3、get操作
- 第一次计算key的hash,找到Segment元素的位置;
- 第二次计算key的hash,找到HashEntry数组的位置;
- 遍历链表,匹配就返回,否则返回null;
4、size操作(两种方式)
- 采用不加锁的方式,多次计算count,(最多三次),比较结果,如果相同的话,就准备,否则的话计算的结果不准确;
- 采用加锁的方式,给所有Segment元素加锁,计算出count值,这个是准确的;
二、ConcurrentHashMap 1.8
1、数据结构
Node数组 + 链表 + 红黑树,并发操作使用的是Synchronized和CAS来控制;
2、put操作
- 如果没有初始化就先调用initTable()方法来进行初始化过程;
- 如果没有hash冲突就直接CAS插入;
- 如果还在进行扩容操作就先进行扩容;
- 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入;
- 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环;
- 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容;
3、get操作
- 计算hash值,定位到该table索引位置,如果是首节点符合就返回;
- 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回;
- 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null;
4、size操作
直接返回count
总结:
ConcurrentHashMap 1.7 Segment + HashEntry + ReentrantLock 锁粒度为Segment
ConcurrentHashMap 1.8 HashEntry + synchronized + CAS + 红黑树 锁粒度为HashEntry