《集合》之concurrentHashmap
JDK1.8的 ConcurrentHashMap 采用的数据结构跟HashMap1.8的结构一样,数组+链表+红黑树;
摒弃了Segment的概念,通过并发控制 synchronized 和CAS来操作保证线程的安全。
put操作
1)判断数组是否为空,为空进行初始化
2)不为空,则计算 key 的 hash 值,通过(n - 1) & hash计算哈希槽;
3)查看哈希桶是否存在数据,没有数据通过CAS插入
4)存在数据,是否处于迁移状态,
是的话协助迁移
不是的话:锁住该槽,插入元素、释放槽上的锁
5)需要将链表转红黑树:锁住该槽、构建红黑树替换链表、释放槽上的锁
6)插入完成之后判断当前节点数是否大于阈值,若大于,则扩容为原数组的二倍
Node节点
默认桶上的结点就是Node结点。Node只有一个next指针,是一个单链表,提供find方法实现链表查询
当出现hash冲突时,Node结点会首先以链表的形式链接到table上,当结点数量超过一定数目时,链表会转化为红黑树。
TreeNode节点
TreeNode就是红黑树的结点,TreeNode不会直接链接到table[i]——桶上面,而是由TreeBin链接,TreeBin会指向红黑树的根结点。
TreeBin节点
TreeBin会直接链接到table[i]——桶上面,该结点提供了一系列红黑树相关的操作,以及加锁、解锁操作。
另外TreeBin提供了一系列的操作
- TreeBin(TreeNode<K,V> b),将以b为头结点的链表转换为红黑树.
- lockRoot(),对红黑树的根结点加写锁
- unlockRoot(),释放写锁
- find(int h, Object k),从根结点开始遍历查找,找到“相等”的结点就返回它,没找到就返回null,当存在写锁时,以链表方式进行查找,不阻塞读锁。
ForwardingNode
ForwardingNode 在table扩容时使用,内部记录了扩容后的table,即nexttable。
当table需要扩容时,依次遍历table中的每个槽,如果不为null,把所有元素根据hash值放入扩容后的nexttable中,在原table的槽内放置一个ForwardingNode 。
1、ForwardingNode是一种临时结点,在扩容进行中才会出现,hash值固定为-1,且不存储实际数据。
2、如果旧table数组的一个hash桶中全部的结点都迁移到了新table中,则在这个桶中放置一个ForwardingNode。
3、读操作碰到ForwardingNode时,将操作转发到扩容后的新table数组上去执行;
写操作碰见它时,则尝试帮助扩容,扩容是支持多线程一起扩容。
ReservationNode
在并发场景下、在从 Key不存在 到 插入 的 时间间隔内,为了防止哈希槽被其他线程抢占,当前线程会使用一个reservationNode节点放到槽中并加锁,从而保证线程安全。
- 保留结点.
- hash值固定为-3, 不保存实际数据
- 只在computeIfAbsent和compute这两个函数式API中充当占位符加锁使用