图说跳表

原理 - “二分查找”的链表

二分查找专指对排序后的数组,通过每次查找中间元素,从而支持以log(n)的时间复杂度的快速查找。但是数组的插入操作是O(n)的时间复杂度,面对复杂的需求,需要有“二分查找”的链表来同时支持快速的插入、删除。

此时可以有两个选择:

  • 平衡二叉树(n叉树)
  • 跳表 SkipList

今天我们的主角是跳表(SkipList),树(Tree)就不赘述了。通过下图,我们可以看到跳表的结构:

层数由高到低
Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|---------------->|D|--------------------->|I|->null
+-+                 +-+                      +-+
 | down              |                        |
 v                   v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->|D|------>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
|0|->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

如果我们要查找G(查找路径用两条杠标出),需要从最顶层的头节点到D,然后向下,发现 D < G < I,然后再向下,向右到了F,发现 F < G < I,然后继续向下(下一层的F)向右,最终找到了G:

Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|================>|D|--------------------->|I|->null
+-+                 +-+                      +-+
 | down              ║                        |
 v                   v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->|D|======>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         ║              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->|D|->|E|->|F|=>|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

跳表通过随机的索引来实现近似log(n)的查找,通过最底层的链表实现O(1)的插入、删除,这就是跳表的核心。

Java实现 - ConcurrentSkipList

由Java通常的命名规范我们知道,ConcurrentSkipList是并发跳表,并且其实现是通过CAS实现的无锁容器,所以代码上会复杂一些,接下来我们图说跳表。

数据增删

通过几张图,我们就能知道ConcurrentSkipList的数据增删是如何工作的。

增加一个n非常简单直接:


       +------+                     +------+
  ...  |   b  |-------------------->|   f  | ...
       +------+                     +------+

           |----------------------------|
           |                            v
       +------+       +------+      +------+
  ...  |   b  |       |   n  |----->|   f  | ...
       +------+       +------+      +------+

       +------+       +------+      +------+
  ...  |   b  |------>|   n  |----->|   f  | ...
       +------+       +------+      +------+

删除一个n有点复杂:


       +------+       +------+      +------+
  ...  |   b  |------>|   n  |----->|   f  | ...
       +------+       +------+      +----+

       +------+       +------+       +------+
  ...  |   b  |------>| null |------>|   f  | ...
       +------+       +------+       +------+

       +------+       +------+      +------+       +------+
  ...  |   b  |------>| null |----->|marker|------>|   f  | ...
       +------+       +------+      +------+       +------+

       +------+                                    +------+
  ...  |   b  |----------------------------------->|   f  | ...
       +------+                                    +------+

总共三步:

  • 设置value为null
  • 增加一个marker节点,表示这个节点应该被删除
  • 更新前序节点的next指针

其他线程在查找、遍历等过程中,都会并发的协助完成删除的后面两步(work-steal),所以并不一定是当前线程完成的后两步。

可是删除为什么要这么复杂呢?

为什么删除需要如此麻烦的处理?

让我们看个例子就明白了。

假设我们原来的链表长这样:


       +------+       +------+      +------+
  ...  |   1  |------>|   3  |----->|   5  | ...
       +------+       +------+      +------+

如果我们在删除3时,简单直接的话,就是这样直接使用CAS更新1.next

           |---------------------------|
           |                           v
       +------+       +------+      +------+
  ...  |   1  |       |   3  |----->|   5  | ...
       +------+       +------+      +------+

但是如果与此同时,我们插入了4(通过CAS更新3.next):

           |---------------------------|
           |                           v
       +------+       +------+      +------+
  ...  |   1  |       |   3  |      |   5  | ...
       +------+       +------+      +------+
                          |             ^
                          |         +---|--+
                          |-------->|   4  |
                                    +------+

我们会发现,新插入的元素4丢失了,即使我们通过CAS保证了1.next3.next的正确性,但是我们还是丢失了元素,说明这样删除(或者插入)的无锁算法是错误的。

那么要如何解决呢?

  • 可能我们下意识的想要增加一个3.pre来感知节点已经被删除了,但是这样就变成双向链表了,维护并发更新变得更加复杂;
  • 其实3的前序就是1.next,所以,如果能同时比较1.next3.next是否发生了变化,再set就可以了。但是目前计算机并不支持这种双重比较,所以也不行;

那么直接修改next指针为什么会出问题呢?问题的本质在于,插入和删除虽然针对的是同一个节点(都是针对3),但是却不是同一个字段(1.next3.next),所以CAS失效了(因为CAS只能针对一个值)。那么,如果,插入和删除,都通过3.next来做并发控制,不是就可以了吗?这就是这个算法的核心。

原论文(A Pragmatic Implementation of Non-Blocking Linked Lists)通过先标记被删除节点的next指针,再修改前序节点的next:

       +------+       +------+      +------+
  ...  |   1  |------>|   3|X|----->|   5  | ...
       +------+       +------+      +------+

此时如果4要插入,就会发现3.next是更新过的了(标记删除是对这个指针进行修改),CAS失败,不能插入,需要重新查找插入位置(1.next):

       +------+       +------+      +------+
  ...  |   1  |------>|   3|X|----->|   5  | ...
       +------+       +------+      +------+
                                        ^
                     CAS(3.next)    +---|--+
                           -------->|   4  |
                                    +------+

Java的实现中,考虑到效率问题,没有使用AtomicMarkableReference来支持node.next的做标记,而是通过追加一个marker节点来实现。

索引增删

索引新增发生在新数据插入后,逻辑就是先构建垂直的链表(固定的down),再把这个链表,插入二维的跳表中。 例如当我们已经通过索引,找到了插入点,并且将D插入到了底层的数据链表中。现在要插入F节点的索引了。我们随机确定L=2,于是构建了如下索引节点(注意,索引节点指向的是同一个数据对象,并非多个D):

+-+
|D| // 最高层:2
+-+
 | 
 v 
+-+
|D|
+-+
 | 
 v 
+-+
|D| // 最底层:0
+-+ 

其实此时最底层已经在链表中了(因为是先插入数据,再构建索引):

Head nodes          Index nodes
+-+    right                                 +-+
|2|----------------------------------------->|I|->null
+-+       +-+   +-+                          +-+
 | down   |D|-->|D|---                        |
 v        +-+   +-+  |                        v
+-+            +-+   |        +-+            +-+       +-+
|1|----------->|C|---|------->|F|----------->|I|------>|K|->null
+-+            +-+   |        +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

将最顶层的D链接进跳表中之后:

Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|---------------->|D|--------------------->|I|->null
+-+                 +-+  +-+                 +-+
 | down              |-->|D|                  |
 v                       +-+                  v
+-+            +-+        |   +-+            +-+       +-+
|1|----------->|C|->------|- >|F|----------->|I|------>|K|->null
+-+            +-+    ____|   +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

下一层链接进入之后:

Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|---------------->|D|--------------------->|I|->null
+-+                 +-+                      +-+
 | down              |                        |
 v                   v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->|D|------>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

索引删除前提是数据删除,例如我们删除了D的话,因为索引都是引用的同一个数据节点,都会变为空:

Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|---------------->| |--------------------->|I|->null
+-+                 +-+                      +-+
 | down              |                        |
 v                   v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->| |------>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->| |->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

然后后续的查找过程中,遇到空节点都会直接从链表中移除:

Head nodes          Index nodes
+-+    right                                 +-+
|2|----------------------------------------->|I|->null
+-+         +-+                              +-+
 | down     | |------|                        |
 v          +-+      v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->| |------>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->| |->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
```
Head nodes          Index nodes
+-+    right                                 +-+
|2|----------------------------------------->|I|->null
+-+         +-+        +-+   +-+             +-+
 | down     | |------->| | ->| |              |
 v          +-+        +-+   +-+              v
+-+            +-+            +-+            +-+       +-+
|1|----------->|C|----------->|F|----------->|I|------>|K|->null
+-+            +-+            +-+            +-+       +-+
 v              |              |              |         |
Nodes  next     v              v              v         v
+-+  +-+  +-+  +-+       +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|------>|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+       +-+  +-+  +-+  +-+  +-+  +-+  +-+
```

有同学可能有疑问,为什么删除索引没有像删除数据一样,采用marker的方式,而是直接删除了,这样不会导致问题吗? 答案是会!但是不会影响正确性,我们继续看例子。假如我们在删除D的同时,插入了E的索引,最顶层(第二层)已经正确的插入了,但是插入第1层索引时,遇到了并发问题:

Head nodes          Index nodes
+-+    right             +-+                 +-+
|2|--------------------->|E|---------------->|I|->null
+-+       +-+  down      +-+                 +-+
 | down   | |--------|    |                   |
 |        +-+   |----|----|----|              |
 v              |    v    v    v              v
+-+            +-+  +-+  +-+  +-+            +-+       +-+
|1|----------->|C|  | |->|E|->|F|----------->|I|------>|K|->null
+-+            +-+  +-+  +-+  +-+            +-+       +-+
 v              |    |    |    |              |         |
Nodes  next     v    v    v    v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->| |->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

如同我们之前在描述删除数据时举的例子,第一层索引中的E被链接到了已经被删除的节点(原来的D),我们把删除先进行完,方便查看新的结构:

Head nodes          Index nodes
+-+    right             +-+                 +-+
|2|--------------------->|E|---------------->|I|->null
+-+                      +-+                 +-+
 | down                   v                   |
 |                       +-+                  |
 v                       |E|---v              v
+-+            +-+       +-+  +-+            +-+       +-+
|1|----------->|C|--------|-->|F|----------->|I|------>|K|->null
+-+            +-+        |   +-+            +-+       +-+
 v              |         |    |              |         |
Nodes  next     v         v    v              v         v
+-+  +-+  +-+  +-+       +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|------>|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+       +-+  +-+  +-+  +-+  +-+  +-+  +-+

我们可以发现本来应该在第一层索引的E的节点,并不在完全在第一层(从第一层开始向右遍历,并不会遇到E;由上层的E可以正常通过E查找到F.)。但是我们注意到,跳表本身的性质(即每层都是排序的,纵向元素一致,纵向向右可以找到更大的元素)并没有被破坏,只是可能查找性能会略有影响。这便是JavaConcurrentSkipList的在插入性能和查询性能之间的折中。


ConcurrentSkipList JDK8 源码详细解析

源码部分比较长,解析将通过代码中的中文注释(英文注释为源码自带)进行,方便大家理解。

我们先把跳表的结构再回顾一下,方便我们理解源码:

层数由高到低
Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|---------------->|D|--------------------->|I|->null
+-+                 +-+                      +-+
 | down              |                        |
 v                   v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->|D|------>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         |              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

基础的数据结构:

/*
 跳表最高层头节点
 */
private transient volatile HeadIndex<K,V> head;

static final class HeadIndex<K,V> extends Index<K,V> {
    final int level; //层高(数据在0层,1-MAX为索引
    ...
}
static class Index<K,V> {
    final Node<K,V> node; //索引中的数据节点,纵向节点指向同一个数据对象,方便一同修改
    final Index<K,V> down; //下面的索引节点
    volatile Index<K,V> right; //右边的索引节点
    ...
}

局部变量常见含义:

Node:         b, n, f    前序节点,当前节点,后续节点
Index:        q, r, d    当前索引节点,右节点,下节点.
              t          另一个索引节点
Head:         h
Levels:       j
Keys:         k, key
Values:       v, value
Comparisons:  c

增删都用到的关键方法:查找前序节点(顺便清理删除节点)

private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
    if (key == null)
        throw new NullPointerException(); // don't postpone errors
    for (;;) {// 循环直到直接return
        for (Index<K,V> q = head, r = q.right, d;;) { //从head开始往右查找
            if (r != null) {// 没到某一层的最右端
                Node<K,V> n = r.node;
                K k = n.key;
                if (n.value == null) {// 已被标记删除
                    if (!q.unlink(r))// 协助删除
                        break;           // restart 协助删除失败
                    r = q.right;         // reread r
                    continue;
                }
                if (cpr(cmp, key, k) > 0) {//key大于右节点,向右移动
                    q = r;
                    r = r.right;
                    continue;
                }// 找到了第一个不比key大的节点:q < key <= r
            }
            if ((d = q.down) == null)//向下查找
                return q.node;//到达了最底层
            q = d;
            r = d.right;
        }
    }
}

觉得不好理解的话,可以结合查找G的例子的图再看:

Head nodes          Index nodes
+-+    right        +-+                      +-+
|2|================>|D|--------------------->|I|->null
+-+                 +-+                      +-+
 | down              ║                        |
 v                   v                        v
+-+            +-+  +-+       +-+            +-+       +-+
|1|----------->|C|->|D|======>|F|----------->|I|------>|K|->null
+-+            +-+  +-+       +-+            +-+       +-+
 v              |    |         ║              |         |
Nodes  next     v    v         v              v         v
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
| |->|A|->|B|->|C|->|D|->|E|->|F|=>|G|->|H|->|I|->|J|->|K|->null
+-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+

新增(put(k, v))的核心逻辑,包含新增数据和新增索引:

private V doPut(K key, V value, boolean onlyIfAbsent) {
    Node<K,V> z;             // added node
    if (key == null)
        throw new NullPointerException();
    Comparator<? super K> cmp = comparator;
    outer: for (;;) { // 无限循环,直到插入成功,通过 break outer 退出
        for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
            if (n != null) { // 前序节点不是最后一个节点
                Object v; int c;
                Node<K,V> f = n.next;
                if (n != b.next) // inconsistent read //并发增删发生了,重新获取前序节点
                    break;
                if ((v = n.value) == null) {   // n is deleted
                    n.helpDelete(b, f); // 协助添加删除marker或者去除节点n
                    break;
                }
                if (b.value == null || v == n) // b is deleted
                    break; // v == n 说明 n.value 是一个节点,是marker
                if ((c = cpr(cmp, key, n.key)) > 0) { // 如果 key > n.key, 继续向右
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) {// 如果 key == n.key,找到了相同的key
                    if (onlyIfAbsent || n.casValue(v, value)) {
                        @SuppressWarnings("unchecked") V vv = (V)v;
                        return vv;
                    }
                    break; // restart if lost race to replace value
                }
                // else c < 0; fall through
            }

            // 满足:b.key < key < n.key,找到了插入点
            z = new Node<K,V>(key, value, n);
            if (!b.casNext(n, z))// cas修改
                break;         // restart if lost race to append to b
            break outer; //结束循环,继续更新索引
        }
    }

    int rnd = ThreadLocalRandom.nextSecondarySeed(); //获取随机数
    // 插入索引(1/4概率)
    if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
        // rnd == 0?
        // 最多31层
        int level = 1, max;
        while (((rnd >>>= 1) & 1) != 0) // 第level位bit是1的话,继续循环;0的话,结束
            ++level;
        Index<K,V> idx = null;
        HeadIndex<K,V> h = head;//最上层的头节点
        if (level <= (max = h.level)) {// 插入层级小于现有最高层级
            for (int i = 1; i <= level; ++i)
                idx = new Index<K,V>(z, idx, null);//构建从leve->1向下的index链表
        }
        else { // try to grow by one level
            level = max + 1; // hold in array and later pick the one to use
            @SuppressWarnings("unchecked")Index<K,V>[] idxs =
                (Index<K,V>[])new Index<?,?>[level+1];
            for (int i = 1; i <= level; ++i)
                idxs[i] = idx = new Index<K,V>(z, idx, null);//构建index节点向下的链表
            for (;;) {
                h = head;
                int oldLevel = h.level;
                if (level <= oldLevel) // lost race to add level
                    break;
                HeadIndex<K,V> newh = h;
                Node<K,V> oldbase = h.node;
                for (int j = oldLevel+1; j <= level; ++j)
                    //以新加元素作为右节点,原头节点做down节点,构建二维跳表索引
                    newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                if (casHead(h, newh)) {//设置新头节点
                    h = newh;
                    idx = idxs[level = oldLevel];
                    break;
                }
            }
        }
        // find insertion points and splice in
        splice: for (int insertionLevel = level;;) {
            int j = h.level;
            for (Index<K,V> q = h, r = q.right, t = idx;;) {
                if (q == null || t == null)//找到了当前level的最右侧
                    break splice;
                if (r != null) {
                    Node<K,V> n = r.node;
                    // compare before deletion check avoids needing recheck
                    int c = cpr(cmp, key, n.key);
                    if (n.value == null) {//index-r被标记删除
                        if (!q.unlink(r))
                            break;
                        r = q.right;
                        continue;
                    }
                    if (c > 0) {//插入的key在比r大,继续往右
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                // 达成:q < key < r

                if (j == insertionLevel) {// 当前是插入层
                    if (!q.link(r, t))
                        break; // restart
                    if (t.node.value == null) {
                        findNode(key);
                        break splice;
                    }
                    if (--insertionLevel == 0)//插入层减一;到达最底层,退出
                        break splice;
                }
                // 在新增索引的纵向范围内,t更新为下一层,以便插入下一层的链表中
                if (--j >= insertionLevel && j < level)
                    t = t.down;
                q = q.down; //向下搜索
                r = q.right;
            }
        }
    }// else : rnd中0、31位是1,不插入索引(3/4概率)
    return null;
}

总结一下,索引新增逻辑:

  • 新增z节点
  • 通过随机,确定其插入层级L
  • 构建该节点,从0层到L的索引链表(垂直方向)
  • 如果L > head.level(最高层)
    • 每层构建 head -> z 索引
  • 1 -> min(L, head.level)索引插入每一层
    • 找到第一个比z大元素,链接进去

删除KV的核心逻辑

final V doRemove(Object key, Object value) {
    if (key == null)
        throw new NullPointerException();
    Comparator<? super K> cmp = comparator;
    outer: for (;;) {
        for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
            Object v; int c;
            if (n == null) // key对应的节点已经被删除
                break outer;
            Node<K,V> f = n.next;
            if (n != b.next)                    // inconsistent read
                break;
            if ((v = n.value) == null) {        // n is deleted
                n.helpDelete(b, f);
                break;
            }
            if (b.value == null || v == n)      // b is deleted
                break; // v == n: 你是deletion marker
            if ((c = cpr(cmp, key, n.key)) < 0) // key已经被删除,结束
                break outer;
            if (c > 0) {// 有并发插入发生,继续向右
                b = n;
                n = f;
                continue;
            }
            if (value != null && !value.equals(v))//value不符合
                break outer;
            if (!n.casValue(v, null))// 标记value为null
                break;//并发标记失败
            // 标记null成功
            if (!n.appendMarker(f) || !b.casNext(n, f))
                // 既没有增加marker成功,也没有去除n成功,重试
                findNode(key);                  // retry via findNode
            else {
                findPredecessor(key, cmp);      // clean index //遇到空节点顺便从跳表中移除
                if (head.right == null)//被删除的节点是该层唯一节点,tryReduceLevel
                    tryReduceLevel();
            }
            @SuppressWarnings("unchecked") V vv = (V)v;
            return vv;
        }
    }
    return null;
}

删除空层(由于删除索引引起)

private void tryReduceLevel() {
    HeadIndex<K,V> h = head;
    HeadIndex<K,V> d;
    HeadIndex<K,V> e;
    if (h.level > 3 &&
        (d = (HeadIndex<K,V>)h.down) != null &&
        (e = (HeadIndex<K,V>)d.down) != null &&
        e.right == null && 
        d.right == null &&
        h.right == null &&  // 最顶层,连续三层为空
        casHead(h, d) && // try to set // 减少一层
        h.right != null) // recheck // 再次查询,防止并发插入了元素,即本层不为空了
        casHead(d, h);   // try to backout // 如果并发使得本层不为空了,恢复head
}

参考:

猜你喜欢

转载自juejin.im/post/7114173670884540423