万字长文,ConcurrentSkipListMap源码详解( 四 )
(1)根据给定的key从跳表的左上方往右或者往下查找到Node链表的前驱Node结点 , 这个查找过程会删除一些已经标记为删除的结点 。
(2)找到前驱结点后 , 开始往后插入查找插入的位置(因为找到前驱结点后 , 可能有另外一个线程在此前驱结点后插入了一个结点 , 所以步骤 1 得到的前驱现在可能不是要插入的结点的前驱 , 所以需要往后查找) 。
(3)随机生成一个种子 , 判断是否需要增加层级 , 并且在各层级中插入对应的 Index 结点 。
友情提示: doPut 的内容较多 , 不感兴趣的伙伴可以跳过 , 知道大概流程即可 。
源码static final class Values
findPredecessor 找到前继节点这个方法我们在 skiplist 中其实也有 。
就是从左往右 , 从上往下找 。
/** * 找到最底层的节点 , key 严格小于给定的 key 。* 或者返回 base-level 的 header 节点 , 如果元素不存在 。* * 同时在遍历的过程中 , 还会把删除的节点进行 unlink 操作 。* 调用者依靠清除索引删除节点的这种副作用 。* ** @author 老马啸西风 */private Node
findNode 查找节点除了查找前继节点 , 上面还有一个查询 Node 的方法 , 也值得我们注意 。
返回保持键的节点;如果没有 , 则返回null , 清除沿途看到的所有已删除节点 。 反复遍历基本级别 , 以查找从findPredecessor返回的前任者开始的键 , 并在遇到时处理基本级别的删除 。
实际上上面调用的时候 , 也正是利用这种清除无效节点的副作用(side effect) 。
在以下情况下 , 重新启动将以节点n为中心进行遍历:
(1)在读取n的下一个字段之后 , 不再假定n是前任b的当前后继者 , 这意味着我们没有一致的3节点快照 , 因此无法取消链接遇到的任何后续已删除节点 。
(2)n的value字段为null , 表示n被删除 , 在这种情况下 , 我们在重试之前帮助进行正在进行的结构删除 。
即使在某些情况下这种取消链接不需要重新启动 , 也不会在此处进行分类 , 因为这样做通常不会超过重新启动的成本 。
(3)n是标记 , 或者n的前任值字段为null , 表示(除其他可能性外)findPredecessor返回了已删除的节点 。
我们无法取消链接该节点 , 因为我们不知道它的前任节点 , 因此请依赖另一个调用findPredecessor的通知并返回一些更早的前任节点 , 它将执行此操作 。
仅在循环开始时才需要严格执行此检查(并且根本不需要严格进行b.value检查) , 但每次迭代均需执行此检查 , 以帮助避免调用者与其他线程的争用 , 因为它们将无法更改链接 , 因此无论如何都会重试 。
doPut , doRemove和findNear中的遍历循环都包含相同的三种检查 。
专门的版本出现在findFirst和findLast及其变体 。
他们不容易共享代码 , 因为每个人都使用执行顺序执行的本地人读取字段 。
/** * Nodes heading each level keep track of their level. */static final class HeadIndex
插入的例子为了让大家更好地理解这些源码 , 我们和大家一起看一个例子 。
- 微信|万字长文:谈谈我对视频号的思考
- 中文|万字长文:谷歌进入到退出中国市场的前因后果
- 万字详文:微软 VSCode IDE 源码分析揭秘
- 降噪耳机大PK:Sony、Bose、Skullcandy谁更强(万字干货)
- 万字详文干货:从无盘启动volumio看Linux启动原理
- 万字详文:深入理解 Lua 虚拟机
- 万字长文把 VSCode 打造成 C++ 开发利器,解决你的多重开发需求
- 谈论|B站百万粉丝up主发长文,谈论视频限流问题,B站审核过于严苛?
- Redmi Note系列全球销量破1.4亿 卢伟冰发长文总结成功原因
- Note|Redmi Note系列全球销量破1.4亿 卢伟冰发长文总结成功原因