文章目录
前言
此乃随笔, 用于记录ConcurrentHashMap的扩容逻辑, 这位大神 JDK1.8–深度分析CONCURRENTHASHMAP原理分析 的文章给了我莫大的鼓励。
顺带, 记录了HashMap的扩容细则。
至今为止, 我依旧没懂 ConcurrentHashMap 的扩容细则。
ConcurrentHashMap resize/rehash
1. 什么时候resize
跟HashMap一样, 使用
Node数组
, 数组有大小Capacity
;
数组元素上挂Node结点, 可以是单链表(不过8位), 可以是红黑树。
.
因为是并发HashMap, 没有size
字段(即时统计)。
加载因子loadFactor
, 1.8已经不再使用。
- 每个Node结点的长度
> 8
时;- 如果数组大小
< 64
, 优先扩容resize(rehash)
- 大于64,优先转红黑树。转时锁逻辑与
put
锁逻辑一致。
- 如果数组大小
- ConcurrentHashMap元素个数(
size
)>
sizeCtl
(0.75 * tab.length
)- 每次
resize(rehash)
我完毕之后会一直检查是否需要再次resize(rehash)
- 源码判断逻辑:
s >= (long)(sc = sizeCtl)
(其中s=ConcurrentHashMap.size()
) - 源码判断逻辑:sizeCtl = (n << 1) - (n >>> 1); (其中n=resize前的size)。
- 0.75:
2n - 0.5n = 1.5n
- resize后的大小
2n
1.5n / 2n = 0.75
。
- 0.75:
- 每次
2. 怎么resize
这块没看透。看透再补充
.
ConcurrentHashMap
始终维护一个Node[] table
表示当前的数据, 以及一个Node[] newTab
表示将要resize去的表。
- 多线程
resize(rehash)
1. 每个线程负责16个Node, 最多允许 数组有大小Capacity
/16
个线程。
2. 正在执行put的那个线程,也可能参与resize(rehash)
3. 参与resize(rehash)
, 仅在当前key对应的Node
结点已经被转移,否则加锁继续put。 - resize是把旧
Node
转移到一个新的Node
数组(源码2410
行);
1. resieze结束的标记(逻辑非常复杂,没懂,参阅 JDK1.8–深度分析CONCURRENTHASHMAP原理分析 的sizeCtl扩容退出机制章节), 或者直接看源码2414
行。
2. 每一个被转移完毕的Node
结点的hash = -1
(ForwardingNode
); rehash
的方式和HashMap
的实现方式一致
1. 如果是链表, 把链表的每个结点rehash到新的table中。
2. 如果是红黑树, 也是一样的执行逻辑。
关于加锁退出机制, 以及Node结点长度大于8时, 以及创建table时, 逻辑相似。 都是
resize(rehash)
的过程。
HashMap resize/rehash
这个相对来说, 单线程, 就比较好理解了、当
size
>table.length * 0.75
时resize/rehash
执行。
- resize 直接将 旧 table 赋值为一个
2倍大小
的新table
1. 因此resize的时候可能导致get得到null
(实际有值)。(多线程环境)
2. 源码:Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
&&table = newTab;
- 迭代每个数组元素
Node
, 对链表和红黑树迁移。 - 单节点直接迁移(
next
= null)
1. 数组位置算法:(table.length - 1) & hash
2. table.length 为2^n, 则(table.length - 1)
全都是二进制的1
;
3. 扩容前后的值都是hash的值, 之前为null, 之后也还是为null. 可以直接赋值。 - 链表和红黑树分别rehash到新的table上去。
Redis resize/rehash
据说是 渐进式 rehash,https://www.cnblogs.com/meituantech/p/9376472.html 看不懂。