最近在找工作,也在夯实基础。今天差不多读了一整天的 并发map,特记录一点收获。
如何保证在并发情况下resize扩容时的安全性。
1、两个数组变量,均是成员变量(table nextTable),迁移时是以桶为单位,且会用synchronized锁住桶。只要桶迁移完了,会先将生成的新的数组放置到新数组上,那在旧数组对应的位置,cas将元素设置成 ForwardingNode(请注意该特殊节点的构造方法)。
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
2、当在扩容时,有多个线程前来操作map时
2.1、若是put或者remove,则需要定位到对应的桶上且锁住。
2.1.1 如果当前桶正在扩容中进行迁移,那么将无法获取到锁,许等待这个桶处理完毕。
2.1.2 如果获取到锁,则会判断获取锁前后的桶是否一致,只有一致时才会进行put和remove操作
2.1.3 如果当前桶为 ForwardingNode 节点,则表示当前桶的数据已被迁移到新数据,进入 helpTransfer 方法
2.2、进入协助扩容
2.2.1 成功协助扩容,则会将负责的分配给自己的桶迁移完毕。若本线程负责的桶处理完毕,但是其他线程负责的桶未处理完毕,看代码应该是空转cpu(此处逻辑可能没有理清)。
2.2.2 若不需要协助,则会直接返回ForwardingNode节点下 nextTab,去追溯构造方法会发现 nextTab 引用的实例变量 nextTable。然后就在新的数组上直接操作remove或者put。
2.3、若在此时获取指定是值
2.3.1 在旧数组上判断桶是否存在,存在且不是链表,则直接返回数据
2.3.2 旧数组上存在且为 ForwardingNode ,则调用 ForwardingNode.find 方法去新数组上查找数据