概述
ConcurrentSkipListMap是一个线程安全的有序的哈希表,并发安全主要由CAS来实现。内部的使用了跳表这种数据结构。ConcurrentSkipListSet的底层是基于ConcurrentSkipListMap实现的。
跳表(Skip List)
跳表可以做到比较稳定的插入,查询与删除。理论插入查询删除的算法时间复杂度为O(logN).
跳表其实是从链表之上改进而来的。我们知道在链表中查询,插入的时间复杂度都为O(n).如果我们可以在多个节点之间跳跃,就可以提高效率。
链表的结构如下;
改进后得到:
如果层次更多的话,那么结构如下;
跳表的查询:
假如我们要查询11.从最上面一层出发,发现11大于5小于13,那么确定了大致区间。进入第二层,发现11大于9小于13,区间缩小。进入第三层,依次查找,最终找到11这个节点。
插入与查询的过程也非常的类似,首先找到在最底层合适的位置,然后再随机是否向上拓展。
删除同样也需要查找,然后再从下至上依次删除。
ConcurrentSkipListMap源码分析
重要属性
1 | /** |
ConcurrentSkipListMap的跳表实现:
数据存储由三个内部类实现:Node:存储键值对, 单向链表节点。Index:跳表中的索引节点,包含了向右的指针和向下的指针,和节点。HeadIndex:跳表的头,继承至Index,包含了层次信息。
重要方法
put方法
1 | public V put(K key, V value) { |
从put方法中,我们可以知道ConcurrentSkipListMap不支持null键。
实际的put工作由doPut方法完成。
1 | private V doPut(K key, V value, boolean onlyIfAbsent) { |
这个方法的实现非常的复杂。但大致可以分为两个步骤;
- 通过自旋查找索引位置,更新或插入给定的节点元素。在遍历查找的过程中,也会帮助清楚已经删除的节点。具体的流程如下:
- 首先通过
findPredecessor方法,从最底一层找到key节点的前驱节点b,从这个节点开始先后查找合适位置插入。 - 如果在查找的过程中,发现了已删除的节点,那么会调用
helpDelete方法帮助清除节点。
- 通过随机的方式,确定是否更新跳表层级。随机的过程大致如下:首先生成一个随机数
rnd,如果rnd为正偶数,那么就会进行下一步的判断,计算rnd从第2位开始有多少个连续的1,如果连续1的数量小于等于旧表层级,则不需要增加跳表层级,只需要更新index,否则旧需要更新跳表层级。
remove方法
1 | public V remove(Object key) { |
remove实际上是调用了doRemove方法。
1 | final V doRemove(Object key, Object value) { |
整个方法的流程大致如下:
- 首先找到需要删除节点的前系欸但,如果在查找的过程中发现已经删除的节点,那么旧帮助清除节点
- 子啊找打需要删除的节点时,不会理解移除它,而是会通过CAS添加一个删除标识,然后再利用CAS来解除链接,如果途中CAS执行失败,那么就会调用
findNode来删除有删除标记的节点。 - 最后检查
head.right如果已经被移除了,那么就会调用tryReduceLevel方法尝试对跳表进行降级操作(只有层级大于三才可以降级)。
get方法
1 | public V get(Object key) { |
我们可以发现,实际完成get操作的是doGet方法。
1 | private V doGet(Object key) { |