• java集合类源码分析-concurrentSkipMap


    Jdk1.6 JUC源码解析(26)-ConcurrentSkipListMap、ConcurrentSkipListSet

    作者:大飞

    功能简介:
    • ConcurrentSkipListMap是一种线程安全的有序的Map。一般我们使用有序Map,不要求线程安全的情况下,可以使用TreeMap,要求线程安全的话,就可以使用ConcurrentSkipListMap。
    • ConcurrentSkipListMap内部的数据结构是SkipList(跳表),内部Entry顺序是由实现了Comparable的key或者构造时指定的Comparator来保证。和TreeMap一样,对ConcurrentSkipListMap中元素的put、get和remove等操作的平均时间复杂度也是O(log(n))。
     
    源码分析:
    • 在看内部结构之前,先对跳表这种数据结构有个感性的认识,贴个图:

    注:图片来自https://en.wikipedia.org/wiki/Skip_list
     

           ConcurrentSkipListMap源码中也提供了图形化的注释:
    Java代码  收藏代码
    1. * Head nodes          Index nodes  
    2. * +-+    right        +-+                      +-+  
    3. * |2|---------------->| |--------------------->| |->null  
    4. * +-+                 +-+                      +-+  
    5. *  | down              |                        |  
    6. *  v                   v                        v  
    7. * +-+            +-+  +-+       +-+            +-+       +-+  
    8. * |1|----------->| |->| |------>| |----------->| |------>| |->null  
    9. * +-+            +-+  +-+       +-+            +-+       +-+  
    10. *  v              |    |         |              |         |  
    11. * Nodes  next     v    v         v              v         v  
    12. * +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  
    13. * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null  
    14. * +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  
           可见,跳表结构中主要有3中节点:Head节点、Index节点和普通的Node节点。
     
    • 看下源码中这些节点的结构表示:
    Java代码  收藏代码
    1. /** 
    2.  * 节点持有key和value,按顺序链接,单向链表。 
    3.  * 中间可能会链接一些处于中间状态的标记节点。 
    4.  * 链表头节点是一个哑(dummy)节点,可以通过head.node访问。 
    5.  * value域之所以定义成Object(而不是E),是因为还要存放一些针对标记节点和头节点的特殊值(non-V) 
    6.  */  
    7. static final class Node<K,V> {  
    8.     final K key;  
    9.     volatile Object value;  
    10.     volatile Node<K,V> next;  
    11.     /** 
    12.      * 创建一个普通节点。 
    13.      */  
    14.     Node(K key, Object value, Node<K,V> next) {  
    15.         this.key = key;  
    16.         this.value = value;  
    17.         this.next = next;  
    18.     }  
    19.     /** 
    20.      * 创建一个标记节点。 
    21.      * 标记节点和普通节点的重要区别是:标记节点的value域指向自身, 
    22.      * 同时标记节点的key为null。key是否为null在一些地方可以用来  
    23.      * 区分标记节点,但无法区分标记节点和base-level链表头节点, 
    24.      * 因为base-level链表头节点的key也是null。 
    25.      */  
    26.     Node(Node<K,V> next) {  
    27.         this.key = null;  
    28.         this.value = this;  
    29.         this.next = next;  
    30.     }  
    31.     /** Updater for casNext */  
    32.     static final AtomicReferenceFieldUpdater<Node, Node>  
    33.         nextUpdater = AtomicReferenceFieldUpdater.newUpdater  
    34.         (Node.class, Node.class, "next");  
    35.     /** Updater for casValue */  
    36.     static final AtomicReferenceFieldUpdater<Node, Object>  
    37.         valueUpdater = AtomicReferenceFieldUpdater.newUpdater  
    38.         (Node.class, Object.class, "value");  
    39.     /** 
    40.      * compareAndSet value field 
    41.      */  
    42.     boolean casValue(Object cmp, Object val) {  
    43.         return valueUpdater.compareAndSet(this, cmp, val);  
    44.     }  
    45.     /** 
    46.      * compareAndSet next field 
    47.      */  
    48.     boolean casNext(Node<K,V> cmp, Node<K,V> val) {  
    49.         return nextUpdater.compareAndSet(this, cmp, val);  
    50.     }  
    51.     /** 
    52.      * 判断节点是否为标记节点。 
    53.      */  
    54.     boolean isMarker() {  
    55.         return value == this;  
    56.     }  
    57.     /** 
    58.      * 判断节点是否是base-level链表的头节点。 
    59.      */  
    60.     boolean isBaseHeader() {  
    61.         return value == BASE_HEADER;  
    62.     }  
    63.     /** 
    64.      * 尝试在当前节点后面追加一个删除标记节点。 
    65.      */  
    66.     boolean appendMarker(Node<K,V> f) {  
    67.         return casNext(f, new Node<K,V>(f));  
    68.     }  
    69.     /** 
    70.      * 通过追加一个删除标记节点或移除一个标记节点来推进删除。 
    71.      */  
    72.     void helpDelete(Node<K,V> b, Node<K,V> f) {  
    73.         /* 
    74.          * Rechecking links and then doing only one of the 
    75.          * help-out stages per call tends to minimize CAS 
    76.          * interference among helping threads. 
    77.          */  
    78.         if (f == next && this == b.next) {  
    79.             if (f == null || f.value != f) // not already marked  
    80.                 appendMarker(f);  
    81.             else  
    82.                 b.casNext(this, f.next);  
    83.         }  
    84.     }  
    85.     /** 
    86.      * 获取合法的value值。 
    87.      */  
    88.     V getValidValue() {  
    89.         Object v = value;  
    90.         if (v == this || v == BASE_HEADER)  
    91.             //如果是标记节点或者base头节点,返回null。  
    92.             return null;  
    93.         return (V)v;  
    94.     }  
    95.     /** 
    96.      * 为当前映射(Node)创建一个不变(不可修改)的快照。 
    97.      * 如果没有合法值,返回null 
    98.      */  
    99.     AbstractMap.SimpleImmutableEntry<K,V> createSnapshot() {  
    100.         V v = getValidValue();  
    101.         if (v == null)  
    102.             return null;  
    103.         return new AbstractMap.SimpleImmutableEntry<K,V>(key, v);  
    104.     }  
    105. }  
           上面方法中重点关注一下删除方法,删除一个节点分为两步:标记和删除。

            1.假设当前节点为n,n的前驱节点为b,n的后继节点为f,如图: 

    Java代码  收藏代码
    1. +------+       +------+      +------+  
    2. |   b  |------>|   n  |----->|   f  | ...  
    3. +------+       +------+      +------+  
     
          2.现在要删除节点n,那么首先要对n进行标记,如图: 
    Java代码  收藏代码
    1. +------+       +------+      +------+       +------+  
    2. |   b  |------>|   n  |----->|marker|------>|   f  | ...  
    3. +------+       +------+      +------+       +------+  
            可见,要删除节点n,首先是往节点n后面追加一个标记节点。

            3.接下来是删除步骤,直接将节点n和后面的标记节点一起删除,如图: 

    Java代码  收藏代码
    1. +------+                                    +------+  
    2. |   b  |----------------------------------->|   f  | ...  
    3. +------+                                    +------+  
             上面是普通节点,再看下Index节点和Head节点:
    Java代码  收藏代码
    1. /** 
    2.   * Index节点表示跳表的层级。 
    3.   * 注意到Node和Index都有正向的指针,但是它们的类型和作用都不同, 
    4.   * 无法抽象到一个基类里面。 
    5.   */  
    6.  static class Index<K,V> {  
    7.      final Node<K,V> node;  
    8.      final Index<K,V> down;  
    9.      volatile Index<K,V> right;  
    10.      /** 
    11.       * Creates index node with given values. 
    12.       */  
    13.      Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {  
    14.          this.node = node;  
    15.          this.down = down;  
    16.          this.right = right;  
    17.      }  
    18.      /** Updater for casRight */  
    19.      static final AtomicReferenceFieldUpdater<Index, Index>  
    20.          rightUpdater = AtomicReferenceFieldUpdater.newUpdater  
    21.          (Index.class, Index.class, "right");  
    22.      /** 
    23.       * compareAndSet right field 
    24.       */  
    25.      final boolean casRight(Index<K,V> cmp, Index<K,V> val) {  
    26.          return rightUpdater.compareAndSet(this, cmp, val);  
    27.      }  
    28.      /** 
    29.       * 判断当前Index的Node节点是否被删除。 
    30.       */  
    31.      final boolean indexesDeletedNode() {  
    32.          return node.value == null;  
    33.      }  
    34.      /** 
    35.       * 尝试设置新的后继节点。 
    36.       */  
    37.      final boolean link(Index<K,V> succ, Index<K,V> newSucc) {  
    38.          Node<K,V> n = node;  
    39.          newSucc.right = succ;  
    40.          //需要先检测当前Index的Node是否被删除。  
    41.          return n.value != null && casRight(succ, newSucc);  
    42.      }  
    43.      /** 
    44.       * 尝试设置后继节点(right)为后继的后继(越过后继节点) 
    45.       */  
    46.      final boolean unlink(Index<K,V> succ) {  
    47.          return !indexesDeletedNode() && casRight(succ, succ.right);  
    48.      }  
    49.  }  
     
    Java代码  收藏代码
    1. /** 
    2.  * 头节点,每个头节点都包含一个表示层级的域。 
    3.  */  
    4. static final class HeadIndex<K,V> extends Index<K,V> {  
    5.     final int level;  
    6.     HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {  
    7.         super(node, down, right);  
    8.         this.level = level;  
    9.     }  
    10. }  
     
           再看下ConcurrentSkipListMap中的结构: 
    Java代码  收藏代码
    1. public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>  
    2.     implements ConcurrentNavigableMap<K,V>,  
    3.                Cloneable,  
    4.                java.io.Serializable {  
    5.      
    6.     private static final long serialVersionUID = -8627078645895051609L;  
    7.     /** 
    8.      * 用来生成种子的随机数生成器。 
    9.      */  
    10.     private static final Random seedGenerator = new Random();  
    11.     /** 
    12.      * 用来定义base-level的头结点。 
    13.      */  
    14.     private static final Object BASE_HEADER = new Object();  
    15.     /** 
    16.      * 跳表最高层的head index 
    17.      */  
    18.     private transient volatile HeadIndex<K,V> head;  
    19.     /** 
    20.      * 比较器。如果没设置这个比较器,那么久用key的自然序来比较。 
    21.      * @serial 
    22.      */  
    23.     private final Comparator<? super K> comparator;  
    24.     /** 
    25.      * 随机种子,这里没有用volatile修饰,多个线程看到不同的值也没关系。 
    26.      */  
    27.     private transient int randomSeed;  
     
     
    • 大体了解了内部结构,接下来先从简单的构造方法入手分析:
    Java代码  收藏代码
    1. public ConcurrentSkipListMap() {  
    2.     this.comparator = null;  
    3.     initialize();  
    4. }  
    5.   
    6. public ConcurrentSkipListMap(Comparator<? super K> comparator) {  
    7.     this.comparator = comparator;  
    8.     initialize();  
    9. }  

           两个构造方法除了指定比较器的区别外,都调用了initialize方法,看下这个方法: 

    Java代码  收藏代码
    1. /** 
    2.  * 初始化或重置内部状态。 
    3.  */  
    4. final void initialize() {  
    5.     //将内部一些域置空。  
    6.     keySet = null;  
    7.     entrySet = null;  
    8.     values = null;  
    9.     descendingMap = null;  
    10.     //生成随机种子,这个种子用来生成随机的Level。  
    11.     randomSeed = seedGenerator.nextInt() | 0x0100; // 确保非0  
    12.     //生成头节点,该节点value是BASE_HEADER,level是1。  
    13.     head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),  
    14.                               null, null, 1);  
    15. }  
    • 然后分析下put方法:
    Java代码  收藏代码
    1. public V put(K key, V value) {  
    2.     if (value == null)  
    3.         throw new NullPointerException();  
    4.     return doPut(key, value, false);  
    5. }  
    6. private V doPut(K kkey, V value, boolean onlyIfAbsent) {  
    7.     //将原本的key转化成一个可比较的key。  
    8.     Comparable<? super K> key = comparable(kkey);  
    9.     for (;;) {  
    10.         //通过key找到要插入位置的前驱节点(注意这个节点在base_level上)  
    11.         Node<K,V> b = findPredecessor(key);  
    12.         Node<K,V> n = b.next;  
    13.         for (;;) {  
    14.             if (n != null) {  
    15.                 Node<K,V> f = n.next;  
    16.                 if (n != b.next)               //检测一下,如果读取不一致,说明发生竞争,重试。  
    17.                     break;;  
    18.                 Object v = n.value;  
    19.                 if (v == null) {               //节点n已经被删除了  
    20.                     n.helpDelete(b, f);        //删除动作  
    21.                     break;                     //重试。  
    22.                 }  
    23.                 if (v == n || b.value == null) //节点b被删除  
    24.                     break;                     //重试。  
    25.                 int c = key.compareTo(n.key);  
    26.                 if (c > 0) {  
    27.                     //如果c>0,说明当前的节点应该排在n的后面,所以从n后面继续比较。  
    28.                     b = n;  
    29.                     n = f;  
    30.                     continue;  
    31.                 }  
    32.                 if (c == 0) {  
    33.                     //如果onlyIfAbsent为true,那么不进行替换;  
    34.                     //否则需要覆盖旧值。  
    35.                     if (onlyIfAbsent || n.casValue(v, value))  
    36.                         return (V)v;  
    37.                     else  
    38.                         break; // 覆盖时竞争失败,重试。  
    39.                 }  
    40.                 // else c < 0; fall through  
    41.             }  
    42.             //1.构造一个新节点。  
    43.             Node<K,V> z = new Node<K,V>(kkey, value, n);  
    44.             //2.尝试插入b和n之间。  
    45.             if (!b.casNext(n, z))  
    46.                 break;         // 如果尝试插入失败,重试一次。  
    47.             //插入成功后,随机生成一个层级。(这个level不会超过31)  
    48.             int level = randomLevel();  
    49.             if (level > 0)  
    50.                 //level大于0,插入index  
    51.                 insertIndex(z, level);  
    52.             return null;  
    53.         }  
    54.     }  
    55. }  
     
           大概描述一下put方法(里面的一些细节后面分析):
                  1.首先要找出当前节点(其实还没有节点,这里只是给定的key和value)在Node链表(注意这个链表是图中最下层的部分,也就是base-level)中的前驱节点。
                  2.然后从前驱节点往后找到要插入的位置,进行插入。
                  3.插入成功后,可能会生成一个层级。
     

           这里看一下doPut方法中转化key使用的方法: 

    Java代码  收藏代码
    1. private Comparable<? super K> comparable(Object key) throws ClassCastException {  
    2.     if (key == null)  
    3.         throw new NullPointerException();  
    4.     if (comparator != null)  
    5.         return new ComparableUsingComparator<K>((K)key, comparator);  
    6.     else  
    7.         return (Comparable<? super K>)key;  
    8. }  
    9. static final class ComparableUsingComparator<K> implements Comparable<K> {  
    10.     final K actualKey;  
    11.     final Comparator<? super K> cmp;  
    12.     ComparableUsingComparator(K key, Comparator<? super K> cmp) {  
    13.         this.actualKey = key;  
    14.         this.cmp = cmp;  
    15.     }  
    16.     public int compareTo(K k2) {  
    17.         return cmp.compare(actualKey, k2);  
    18.     }  
    19. }  
           其实就是如果指定了比较器,就使用比较器;没指定比较器,就使用Key的自然序(Key需要实现Comparable接口)。
    Java代码  收藏代码
    1. private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) {  
    2.     // 记录下一个要添加的level,以防重试。  
    3.     int insertionLevel = indexLevel;  
    4.     Comparable<? super K> key = comparable(idx.node.key);  
    5.     if (key == null) throw new NullPointerException();  
    6.     // 和findPredecessor过程类似,只是在过程中会添加index节点。  
    7.     for (;;) {  
    8.         int j = h.level;  
    9.         Index<K,V> q = h;  
    10.         Index<K,V> r = q.right;  
    11.         Index<K,V> t = idx;  
    12.         for (;;) {  
    13.             if (r != null) {  
    14.                 Node<K,V> n = r.node;  
    15.                 // compare before deletion check avoids needing recheck  
    16.                 int c = key.compareTo(n.key);  
    17.                 if (n.value == null) {  
    18.                     if (!q.unlink(r))  
    19.                         break;  
    20.                     r = q.right;  
    21.                     continue;  
    22.                 }  
    23.                 if (c > 0) {  
    24.                     q = r;  
    25.                     r = r.right;  
    26.                     continue;  
    27.                 }  
    28.             }  
    29.             if (j == insertionLevel) {  
    30.                 // 这里还需要检查t节点是否被删除,如果t节点被删除,就不能插入。  
    31.                 if (t.indexesDeletedNode()) {  
    32.                     findNode(key); // cleans up  
    33.                     return;  
    34.                 }  
    35.                 //尝试将Index t插入q和q的right节点r之间。  
    36.                 if (!q.link(r, t))  
    37.                     break; // restart  
    38.                 if (--insertionLevel == 0) {  
    39.                     // 最后还要做一次删除检测。  
    40.                     if (t.indexesDeletedNode())  
    41.                         findNode(key);  
    42.                     return;  
    43.                 }  
    44.             }  
    45.             if (--j >= insertionLevel && j < indexLevel)  
    46.                 t = t.down;  
    47.             q = q.down;  
    48.             r = q.right;  
    49.         }  
    50.     }  
    51. }  
     

           看到上述put的第2步过程,可能会疑惑,这是一个一个往后找的,put的时间复杂度看起来更像O(n)啊,其实玄机就在findPredecessor方法里面,看下这个方法: 

    Java代码  收藏代码
    1. /** 
    2.  * 返回最底层(base-level)节点链中比给定key小(在“给定节点”左边)的节点, 
    3.  * 如果没找到,那么返回底层链的头节点。 
    4.  * 在查找过程中会顺手删除帮助删除一点标记为删除的节点。 
    5.  */  
    6. private Node<K,V> findPredecessor(Comparable<? super K> key) {  
    7.     if (key == null)  
    8.         throw new NullPointerException(); // don't postpone errors  
    9.     for (;;) {  
    10.         //将最高层的头节点赋给q。  
    11.         Index<K,V> q = head;  
    12.         //将最高层头结点的右节点赋给r。  
    13.         Index<K,V> r = q.right;  
    14.         for (;;) {  
    15.             if (r != null) {  
    16.                 //如果r不为null,找到r中的数据节点n。  
    17.                 Node<K,V> n = r.node;  
    18.                 K k = n.key;  
    19.                 if (n.value == null) {  
    20.                     //如果n已经被删除,那么尝试推进删除。  
    21.                     if (!q.unlink(r))  
    22.                         break;           // 推进删除失败,重试。  
    23.                     r = q.right;         // 再次读取q的头结点,因为上面删除成功后,q的右节点变了。  
    24.                     continue;  
    25.                 }  
    26.                 //n没被删除的话,和key进行比较。  
    27.                 if (key.compareTo(k) > 0) {  
    28.                     //如果给定的key表示的节点在n后面的话,继续往后找。  
    29.                     q = r;  
    30.                     r = r.right;  
    31.                     continue;  
    32.                 }  
    33.             }  
    34.             //如果r为null,那么往下找。  
    35.             //获取q的下节点d  
    36.             Index<K,V> d = q.down;  
    37.             if (d != null) {  
    38.                 //如果d不为null,将d赋给q,d的右节点赋给r,再次循环。  
    39.                 q = d;  
    40.                 r = d.right;  
    41.             } else  
    42.                 return q.node; //如果d为空,说明q就是最底层的节点,返回这个节点。  
    43.         }  
    44.     }  
    45. }  
           上述过程其实可以简单的理解为,从最高层的头节点开始找,给定key大于当前节点,就往右找,否则就往下找,一直找到最底层链上的节点,这个节点就是给定key在base_level上的前驱节点。
     

           上面的doPut方法中,最后还有一个生成level index的部分,首先调用randomLevel得到一个level值,如果这个值大于0,就调用insertIndex生成一个index,先看下randomLevel: 

    Java代码  收藏代码
    1. /** 
    2.  * Returns a random level for inserting a new node. 
    3.  * Hardwired to k=1, p=0.5, max 31 (see above and 
    4.  * Pugh's "Skip List Cookbook", sec 3.4). 
    5.  * 
    6.  * This uses the simplest of the generators described in George 
    7.  * Marsaglia's "Xorshift RNGs" paper.  This is not a high-quality 
    8.  * generator but is acceptable here. 
    9.  */  
    10. private int randomLevel() {  
    11.     int x = randomSeed;  
    12.     x ^= x << 13;  
    13.     x ^= x >>> 17;  
    14.     randomSeed = x ^= x << 5;  
    15.     if ((x & 0x8001) != 0) // test highest and lowest bits  
    16.         return 0;  
    17.     int level = 1;  
    18.     while (((x >>>= 1) & 1) != 0) ++level;  
    19.     return level;  
    20. }  
           这段代码有些蛋疼...总之就是50%的几率返回0,25%的几率返回1,12.5%的几率返回2...最大返回31。

           继续看insertIndex方法:

    Java代码  收藏代码
    1. /** 
    2.  * 为给定的数据节点创建和添加Index节点。 
    3.  */  
    4. private void insertIndex(Node<K,V> z, int level) {  
    5.     HeadIndex<K,V> h = head;  
    6.     int max = h.level;  
    7.     if (level <= max) {  
    8.         Index<K,V> idx = null;  
    9.         //如果level比当前的max level小,那么创建level个节点,纵向链接起来  
    10.         //(level2的down节点指向level1、level3的down节点指向level2...)  
    11.         for (int i = 1; i <= level; ++i)  
    12.             idx = new Index<K,V>(z, idx, null);  
    13.         //添加index。  
    14.         addIndex(idx, h, level);  
    15.     } else { //如果level比当前的max level大,添加level。  
    16.         /* 
    17.          * 为了减小其他线程在tryReduceLevel方法中检测空level的干扰, 
    18.          * 新的level添加时右节点就已经初始化好了。它们被依次放到一个 
    19.          * 数组里面,当创建新的head index时,会反向访问它们。 
    20.          */  
    21.         //level设置为max+1  
    22.         level = max + 1;  
    23.         //建立一个长度为level的Index数组,  
    24.         Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1];  
    25.         Index<K,V> idx = null;  
    26.         //还是创建level个节点,纵向链接起来,同时将它们放入Index数组。  
    27.         for (int i = 1; i <= level; ++i)  
    28.             idxs[i] = idx = new Index<K,V>(z, idx, null);  
    29.         HeadIndex<K,V> oldh;  
    30.         int k;  
    31.         for (;;) {  
    32.             oldh = head;  
    33.             int oldLevel = oldh.level;  
    34.             if (level <= oldLevel) { // 竞争失败,跳出。  
    35.                 k = level;  
    36.                 break;  
    37.             }  
    38.             HeadIndex<K,V> newh = oldh;  
    39.             Node<K,V> oldbase = oldh.node;  
    40.             for (int j = oldLevel+1; j <= level; ++j)  
    41.                 /* 
    42.                  *这里创建新的HeadIndex,其数据节点为oldBase,down节点为 
    43.                  *之前的head,right节点为上面Index中level最高的节点,level为j 
    44.                  */  
    45.                 newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);  
    46.             //尝试将head设置为新创建的HeadIndex。  
    47.             if (casHead(oldh, newh)) {  
    48.                 k = oldLevel;  
    49.                 break;  
    50.             }  
    51.         }  
    52.         //添加index。  
    53.         addIndex(idxs[k], oldh, k);  
    54.     }  
    55. }  

      

           再继续看下这个addIndex方法:

    Java代码  收藏代码
    1. private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) {  
    2.     // 记录下一个要添加的level,以防重试。  
    3.     int insertionLevel = indexLevel;  
    4.     Comparable<? super K> key = comparable(idx.node.key);  
    5.     if (key == null) throw new NullPointerException();  
    6.     // 和findPredecessor过程类似,只是在过程中会添加index节点。  
    7.     for (;;) {  
    8.         int j = h.level;  
    9.         Index<K,V> q = h;  
    10.         Index<K,V> r = q.right;  
    11.         Index<K,V> t = idx;  
    12.         for (;;) {  
    13.             if (r != null) {  
    14.                 Node<K,V> n = r.node;  
    15.                 // compare before deletion check avoids needing recheck  
    16.                 int c = key.compareTo(n.key);  
    17.                 if (n.value == null) {  
    18.                     if (!q.unlink(r))  
    19.                         break;  
    20.                     r = q.right;  
    21.                     continue;  
    22.                 }  
    23.                 if (c > 0) {  
    24.                     q = r;  
    25.                     r = r.right;  
    26.                     continue;  
    27.                 }  
    28.             }  
    29.             if (j == insertionLevel) {  
    30.                 // 这里还需要检查t节点是否被删除,如果t节点被删除,就不能插入。  
    31.                 if (t.indexesDeletedNode()) {  
    32.                     findNode(key); // cleans up  
    33.                     return;  
    34.                 }  
    35.                 //尝试将Index t插入q和q的right节点r之间。  
    36.                 if (!q.link(r, t))  
    37.                     break; // restart  
    38.                 if (--insertionLevel == 0) {  
    39.                     // 最后还要做一次删除检测。  
    40.                     if (t.indexesDeletedNode())  
    41.                         findNode(key);  
    42.                     return;  
    43.                 }  
    44.             }  
    45.             if (--j >= insertionLevel && j < indexLevel)  
    46.                 t = t.down;  
    47.             q = q.down;  
    48.             r = q.right;  
    49.         }  
    50.     }  
    51. }  

           这个方法要做的事情其实就是:针对一个给定的节点和level值,将之前建立的从上到下的Index节点链接进来,如图: 

     
                  

           当然方法有几个地方要检测当前的idx节点有没有被删除,如果有,要调用一个findNode来做调整,看下这个方法:

    Java代码  收藏代码
    1. /** 
    2.  * 通过给定的key查找对应的数据节点,查找过程中会顺便清理一些  
    3.  * 已经标记为删除的节点。 
    4.  * 一些地方会调用这个方法,不是为了查找节点,而是为了使用清理  
    5.  * 删除节点的这个"副作用"。 
    6.  * 
    7.  * 下列情况出现时,会重新遍历: 
    8.  * 
    9.  *   (1) 在读取了n的next域之后,n不再是b当前的后继节点了,这意味 
    10.  *       着我们没有和之前保持一致的3节点(b->n->f)快照,所以无法 
    11.  *       删除后续节点。 
    12.  * 
    13.  *   (2) 节点n的value域为null,说明n已经被删除。这种情况下,我们 
    14.  *       先帮助推进n节点的删除,然后再重试。 
    15.  * 
    16.  *   (3) n是一个标记节点或者n的前驱节点的value域为null。意味着  
    17.  *       findPredecessor方法会返回一个被删除的节点。我们无法移  
    18.  *       除这节点,因为无法确定它的前驱节点。所以再次调用findPredecessor  
    19.  *       (findPredecessor方法中会处理这个情况)并返正确的前驱节点。 
    20.  */  
    21. private Node<K,V> findNode(Comparable<? super K> key) {  
    22.     for (;;) {  
    23.         //找到key对应的在base_level上的前驱节点。  
    24.         Node<K,V> b = findPredecessor(key);  
    25.         Node<K,V> n = b.next;  
    26.         for (;;) {  
    27.             if (n == null)  
    28.                 return null;  
    29.             Node<K,V> f = n.next;  
    30.             if (n != b.next)                // 读取不一致,说明发生竞争,重试。  
    31.                 break;  
    32.             Object v = n.value;  
    33.             if (v == null) {                // n被删除了,帮助推进n的删除,重试。  
    34.                 n.helpDelete(b, f);  
    35.                 break;  
    36.             }  
    37.             if (v == n || b.value == null)  // b被删除了,重试。  
    38.                 break;  
    39.             //开始比较  
    40.             int c = key.compareTo(n.key);  
    41.             if (c == 0)  
    42.                 return n; //找到对应节点。  
    43.             if (c < 0)  
    44.                 return null;  
    45.             b = n;  
    46.             n = f;  
    47.         }  
    48.     }  
    49. }  
           再次更细致的总结一下put方法:
                  1.首先要根据给定的key找出在base_level链表中对应的前驱节点(从结构图的左上角往右或往下一路找过来),注意put方法使用的log(n)时间主要体现在这个过程,这个查找过程中会顺便帮忙推进一些节点的删除。
                  2.找到前驱节点后,然后从这个前驱节点往后找到要插入的位置(注意当前已经在base_level上,所以只需要往后找),这个查找过程中也会顺便帮忙推进一些节点的删除。。
                  3.找到了要插入的位置,尝试插入,如果竞争导致插入失败,返回到第1步重试;如果插入成功,接下来会随机生成一个level,如果这个level大于0,需要将插入的节点在垂直线上生成level(level<=maxLevel + 1)个Index节点。
     
    • 分析完了put方法,再看下get方法:
    Java代码  收藏代码
    1. public V get(Object key) {  
    2.     return doGet(key);  
    3. }  
    4. private V doGet(Object okey) {  
    5.     //转换成可比较的Key。  
    6.     Comparable<? super K> key = comparable(okey);  
    7.     Node<K,V> bound = null;  
    8.     Index<K,V> q = head;  
    9.     Index<K,V> r = q.right;  
    10.     Node<K,V> n;  
    11.     K k;  
    12.     int c;  
    13.     for (;;) {  
    14.         Index<K,V> d;  
    15.         // 向右遍历,一直到null或者当前给定key(对应的节点)大的节点(bound)  
    16.         // 当前给定key对应的节点应该在bound的左边。  
    17.         if (r != null && (n = r.node) != bound && (k = n.key) != null) {  
    18.             if ((c = key.compareTo(k)) > 0) {  
    19.                 q = r;  
    20.                 r = r.right;  
    21.                 continue;  
    22.             } else if (c == 0) {  
    23.                 Object v = n.value;  
    24.                 return (v != null)? (V)v : getUsingFindNode(key);  
    25.             } else  
    26.                 bound = n;  
    27.         }  
    28.         // 往下找。  
    29.         if ((d = q.down) != null) {  
    30.             q = d;  
    31.             r = d.right;  
    32.         } else  
    33.             break;  
    34.     }  
    35.     // 现在到了base_level,往后找就可以了。  
    36.     for (n = q.node.next;  n != null; n = n.next) {  
    37.         if ((k = n.key) != null) {  
    38.             if ((c = key.compareTo(k)) == 0) {  
    39.                 Object v = n.value;  
    40.                 return (v != null)? (V)v : getUsingFindNode(key);  
    41.             } else if (c < 0)  
    42.                 break;  
    43.         }  
    44.     }  
    45.     return null;  
    46. }  
           可见get的过程大概是这样:从最左最高的头节点开始往右或者往下(通过key的比较)遍历,一直找到base_level,然后往后一直找到给定节点,找不到的话返回null。

           代码中还会看到,如果找到了节点,还会判断节点上的value是否为null。如果不为null,直接返回这个value;如果为null,说明这个节点被删除了(正在删除过程中),那么需要调用一个getUsingFindNode方法,看下这个方法: 

    Java代码  收藏代码
    1. private V getUsingFindNode(Comparable<? super K> key) {  
    2.     for (;;) {  
    3.         Node<K,V> n = findNode(key);  
    4.         if (n == null)  
    5.             return null;  
    6.         Object v = n.value;  
    7.         if (v != null)  
    8.             return (V)v;  
    9.     }  
    10. }  
           此方法中会再次调用前面分析过的findNode方法来查找一个key对应的Node,注意方法中外侧还是包了一层无限循环,为的是避免由于竞争导致findNode方法返回的Node又是一个被删除的节点。
     
    • 继续看下containsKey方法:
    Java代码  收藏代码
    1. public boolean containsKey(Object key) {  
    2.     return doGet(key) != null;  
    3. }  
           containsKey实现非常简单,直接基于doGet方法来做。
     
    • 接着看下remove方法:
    Java代码  收藏代码
    1. public V remove(Object key) {  
    2.     return doRemove(key, null);  
    3. }  
    4. final V doRemove(Object okey, Object value) {  
    5.     //转换成可比较的key  
    6.     Comparable<? super K> key = comparable(okey);  
    7.     for (;;) {  
    8.         //找到key在base_level链上的前驱节点。  
    9.         Node<K,V> b = findPredecessor(key);  
    10.         Node<K,V> n = b.next;  
    11.         for (;;) {  
    12.             if (n == null)  
    13.                 return null;  
    14.             Node<K,V> f = n.next;  
    15.             if (n != b.next)                    // 读取不一致,重试。  
    16.                 break;  
    17.             Object v = n.value;  
    18.             if (v == null) {                    // 如果n被删除了,帮助推进删除,然后重试。  
    19.                 n.helpDelete(b, f);  
    20.                 break;  
    21.             }  
    22.             if (v == n || b.value == null)      // 如果b被删除了,重试。  
    23.                 break;  
    24.             int c = key.compareTo(n.key);  
    25.             if (c < 0)  
    26.                 return null; //如果比找到的前驱节点的后继节点小,说明没有指定的key对应的节点,返回null。  
    27.             if (c > 0) {  
    28.                 //如果比找到的前驱节点的后继节点大,说明目标节点在这个节点后面,往后找。  
    29.                 b = n;  
    30.                 n = f;  
    31.                 continue;  
    32.             }  
    33.             if (value != null && !value.equals(v))  
    34.                 return null;//给定的value和链表中的value不一致,删除失败。  
    35.             if (!n.casValue(v, null))//首先尝试将要删除的目标节点n的value置空。  
    36.                 break; //如果失败,说明发生竞争,重试。  
    37.             /* 
    38.              * 如果上一步将n的value置空成功,接下来首先尝试将n的后面追加一个标记节点, 
    39.              * 成功的话,再尝试将n和标记节点一起移除,这两部有任何一步失败,都会调用 
    40.              * findNode来完成删除(利用findNode方法的副作用) 
    41.              */  
    42.             if (!n.appendMarker(f) || !b.casNext(n, f))  
    43.                 findNode(key);                  // Retry via findNode  
    44.             else {  
    45.                 /* 
    46.                  * 如果上面的n.appendMarker(f)和b.casNext(n, f)都调用成功, 
    47.                  * 然后就会调用这个方法,注意这里其实也是使用这个方法的 
    48.                  * 副作用来删除节点n的Index节点。 
    49.                  */  
    50.                 findPredecessor(key);             
    51.                 if (head.right == null)  
    52.                     // 删除了一些Index之后,这里判断一下head的right节点是否为null,  
    53.                     // 如果为null,说明最高层的Index链已经不存在数据,可以删掉了。  
    54.                     tryReduceLevel();  
    55.             }  
    56.             return (V)v;  
    57.         }  
    58.     }  
    59. }  

           看一下上面方法中最后调用的tryReduceLevel方法: 

    Java代码  收藏代码
    1. private void tryReduceLevel() {  
    2.     HeadIndex<K,V> h = head;  
    3.     HeadIndex<K,V> d;  
    4.     HeadIndex<K,V> e;  
    5.     if (h.level > 3 &&  
    6.         (d = (HeadIndex<K,V>)h.down) != null &&  
    7.         (e = (HeadIndex<K,V>)d.down) != null &&  
    8.         e.right == null &&  
    9.         d.right == null &&  
    10.         h.right == null &&  
    11.         casHead(h, d) && // try to set  
    12.         h.right != null) // recheck  
    13.         casHead(d, h);   // try to backout  
    14. }  
           这个方法看起来有点绕,其实是说:如果最高的前三个HeadIndex都为null(当前看起来),那么就将level减少1层,其实就是将h(当前的head)设置为d(head的下一层),设置完成之后,还有检测h(之前的head)的right是否为null(因为可能刚才由于竞争的原因,导致看到h的right为null),如果这会儿又不是null,那么还得回退回来,再次将head设置为h。
     
    • 再看下containsValue方法:
    Java代码  收藏代码
    1. public boolean containsValue(Object value) {  
    2.     if (value == null)  
    3.         throw new NullPointerException();  
    4.     for (Node<K,V> n = findFirst(); n != null; n = n.next) {  
    5.         V v = n.getValidValue();  
    6.         if (v != null && value.equals(v))  
    7.             return true;  
    8.     }  
    9.     return false;  
    10. }  
           containsValue的实现也比较简答,就是先找到base_level链的第一个节点,然后一直往后找,比较value值。

           看下上面用到的findFirst方法: 

    Java代码  收藏代码
    1. Node<K,V> findFirst() {  
    2.     for (;;) {  
    3.         Node<K,V> b = head.node;  
    4.         Node<K,V> n = b.next;  
    5.         if (n == null)  
    6.             return null;  
    7.         if (n.value != null)  
    8.             return n;  
    9.         n.helpDelete(b, n.next);  
    10.     }  
    11. }  

           就是找到head中node(BASE_HEADER节点)的next,有可能next节点被删除了,所以会做检测,删除的话,推进一下删除,然后继续获取。size和isEmpty也是基于这个方法实现的: 

    Java代码  收藏代码
    1. public int size() {  
    2.     long count = 0;  
    3.     for (Node<K,V> n = findFirst(); n != null; n = n.next) {  
    4.         if (n.getValidValue() != null)  
    5.             ++count;  
    6.     }  
    7.     return (count >= Integer.MAX_VALUE)? Integer.MAX_VALUE : (int)count;  
    8. }  
    9. public boolean isEmpty() {  
    10.     return findFirst() == null;  
    11. }  
    • ConcurrentSkipListMap实现了ConcurrentMap接口,看下这些接口方法的实现:
    Java代码  收藏代码
    1. public V putIfAbsent(K key, V value) {  
    2.      if (value == null)  
    3.          throw new NullPointerException();  
    4.      return doPut(key, value, true);  
    5.  }  
    6.   
    7.  public boolean remove(Object key, Object value) {  
    8.      if (key == null)  
    9.          throw new NullPointerException();  
    10.      if (value == null)  
    11.          return false;  
    12.      return doRemove(key, value) != null;  
    13.  }  
    14.   
    15.  public boolean replace(K key, V oldValue, V newValue) {  
    16.      if (oldValue == null || newValue == null)  
    17.          throw new NullPointerException();  
    18.      Comparable<? super K> k = comparable(key);  
    19.      for (;;) {  
    20.          Node<K,V> n = findNode(k);  
    21.          if (n == null)  
    22.              return false;  
    23.          Object v = n.value;  
    24.          if (v != null) {  
    25.              if (!oldValue.equals(v))  
    26.                  return false;  
    27.              if (n.casValue(v, newValue))  
    28.                  return true;  
    29.          }  
    30.      }  
    31.  }  
    32.   
    33.  public V replace(K key, V value) {  
    34.      if (value == null)  
    35.          throw new NullPointerException();  
    36.      Comparable<? super K> k = comparable(key);  
    37.      for (;;) {  
    38.          Node<K,V> n = findNode(k);  
    39.          if (n == null)  
    40.              return null;  
    41.          Object v = n.value;  
    42.          if (v != null && n.casValue(v, value))  
    43.              return (V)v;  
    44.      }  
    45.  }  
           ConcurrentMap API方法实现中使用到的方法前面都分析过了,可以参考下前面的分析。
     
    • ConcurrentSkipListMap同样实现了SortedMap接口,看下相关接口方法的实现:
    Java代码  收藏代码
    1. public K lastKey() {  
    2.     Node<K,V> n = findLast();  
    3.     if (n == null)  
    4.         throw new NoSuchElementException();  
    5.     return n.key;  
    6. }  

           这里出现了一个findLast方法,之前没分析过,看下: 

    Java代码  收藏代码
    1. Node<K,V> findLast() {  
    2.     Index<K,V> q = head;  
    3.     for (;;) {  
    4.         Index<K,V> d, r;  
    5.         if ((r = q.right) != null) {  
    6.             if (r.indexesDeletedNode()) { //如果发现节点已经删除的Index,顺便移除。  
    7.                 q.unlink(r);  
    8.                 q = head; // 重试。  
    9.             }  
    10.             else  
    11.                 q = r; //向右找  
    12.         } else if ((d = q.down) != null) {  
    13.             q = d; //向下找  
    14.         } else {  
    15.             //现在到了base_level链上,向后找。  
    16.             Node<K,V> b = q.node;  
    17.             Node<K,V> n = b.next;  
    18.             for (;;) {  
    19.                 if (n == null)  
    20.                     //最后定位到节点后需要检测一下是不是baseHead  
    21.                     return (b.isBaseHeader())? null : b;   
    22.                 Node<K,V> f = n.next;            // 读取不一致,有竞争发生,重试。  
    23.                 if (n != b.next)  
    24.                     break;  
    25.                 Object v = n.value;  
    26.                 if (v == null) {                 // n节点被删除,重试。  
    27.                     n.helpDelete(b, f);  
    28.                     break;  
    29.                 }  
    30.                 if (v == n || b.value == null)   // b节点被删除,重试。  
    31.                     break;  
    32.                 b = n;  
    33.                 n = f;  
    34.             }  
    35.             q = head; // restart  
    36.         }  
    37.     }  
    38. }  
           再看一个方法:
    Java代码  收藏代码
    1. public Map.Entry<K,V> lowerEntry(K key) {  
    2.     return getNear(key, LT);  
    3. }  
           这个方法的意思是找到一个比给定key小的所有key里面最大的key对应的Entry,里面调用了getNear方法: 
    Java代码  收藏代码
    1. AbstractMap.SimpleImmutableEntry<K,V> getNear(K key, int rel) {  
    2.     for (;;) {  
    3.         Node<K,V> n = findNear(key, rel);  
    4.         if (n == null)  
    5.             return null;  
    6.         AbstractMap.SimpleImmutableEntry<K,V> e = n.createSnapshot();  
    7.         if (e != null)  
    8.             return e;  
    9.     }  
    10. }  
           getNear方法里面首先通过findNear方法找到指定的Node,然后通过createSnapshot方法返回一个Entry,这个方法最开始的时候看到过。下面重点看下这个findNear方法: 
    Java代码  收藏代码
    1. private static final int EQ = 1;  
    2. private static final int LT = 2;  
    3. private static final int GT = 0; // Actually checked as !LT  
    4. /** 
    5.  * Utility for ceiling, floor, lower, higher methods. 
    6.  * @param kkey the key 
    7.  * @param rel the relation -- OR'ed combination of EQ, LT, GT 
    8.  * @return nearest node fitting relation, or null if no such 
    9.  */  
    10. Node<K,V> findNear(K kkey, int rel) {  
    11.     Comparable<? super K> key = comparable(kkey);  
    12.     for (;;) {  
    13.         Node<K,V> b = findPredecessor(key);  
    14.         Node<K,V> n = b.next;  
    15.         for (;;) {  
    16.             if (n == null)  
    17.                 return ((rel & LT) == 0 || b.isBaseHeader())? null : b; //出口1  
    18.             Node<K,V> f = n.next;  
    19.             if (n != b.next)                  // inconsistent read  
    20.                 break;  
    21.             Object v = n.value;  
    22.             if (v == null) {                  // n is deleted  
    23.                 n.helpDelete(b, f);  
    24.                 break;  
    25.             }  
    26.             if (v == n || b.value == null)    // b is deleted  
    27.                 break;  
    28.             int c = key.compareTo(n.key);  
    29.             if ((c == 0 && (rel & EQ) != 0) ||  
    30.                 (c <  0 && (rel & LT) == 0))  
    31.                 return n; //出口2  
    32.             if ( c <= 0 && (rel & LT) != 0)  
    33.                 return (b.isBaseHeader())? null : b; //出口3  
    34.             b = n;  
    35.             n = f;  
    36.         }  
    37.     }  
    38. }  
           这个方法整体流程其实和findNode类似,都是从head开始先找到base_level的上给定key的前驱节点,然后再往后找。区别是这里传入关系参数-EQ、LT、GT(GT在代码里面没直接用,而是通过没有LT来判断),我们可以通过lowerEntry方法来分析,lowerEntry方法中间接调用的findNear方法传入的是LT,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],所以代码会从findNear中的出口3(见注释)返回。
           接着分析下floorEntry: 
    Java代码  收藏代码
    1. public Map.Entry<K,V> floorEntry(K key) {  
    2.     return getNear(key, LT|EQ);  
    3. }  
           floorEntry方法中间接调用的findNear方法传入的是LT|EQ,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],这里分两种情况:1.如果k<n,那么会从出口3退出,返回b;2.如果k=n,那么会从出口2退出(满足条件(c == 0 && (rel & EQ) != 0)),返回n。
           继续分析下ceilingEntry:
    Java代码  收藏代码
    1. public Map.Entry<K,V> ceilingEntry(K key) {  
    2.     return getNear(key, GT|EQ);  
    3. }  
           ceilingEntry方法中间接调用的findNear方法传入的是GT|EQ,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],这里分两种情况:1.如果k<n(k>b),那么会从出口2退出(满足条件(c <  0 && (rel & LT) == 0)),返回b;2.如果k=n,那么也会从出口2退出(满足条件(c == 0 && (rel & EQ) != 0)),返回n。
           最后分析下higherEntry: 
    Java代码  收藏代码
    1. public Map.Entry<K,V> higherEntry(K key) {  
    2.     return getNear(key, GT);  
    3. }  
           higherEntry方法中间接调用的findNear方法传入的是GT,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],这里分两种情况:1.如果k<n,那么会从出口2退出(满足条件(c <  0 && (rel & LT) == 0)),返回b;2.如果k=n,那么会进入下一次循环,关系变成这样:[b<n=k<f],这时会从出口2退出(满足条件(c <  0 && (rel & LT) == 0)),这时返回的是f。
     
           当然,上面分析的所有方法,都会遇到在findNear中遇到n==null的可能,这时关系图如下[b<k-null],k一定大于b,所以只有传入LT,才可以返回b;否则都是null。而且如果b本身是BaseHead,也只能返回null。
     
    • ConcurrentSkipListMap分析到这里,一些关键的地方已经分析到了,至于其他没覆盖到的方法,基本都是基于上面分析的方法或思路来实现的,这里就不一一分析了。
           最后提一下,看这个类源码的时候,一定要注意几个方面:
           1.注意数据结构,可以仔细理解上面给出的结构图,或者先了解下跳表的背景知识。
           2.注意删除节点是一个标记删除的过程,分两步,记住这个过程;而且源码中好多方法在遍历的过程中都会帮助推进删除。
           3.由于此类是一种无锁并发的实现,所以代码细节上会考虑各种竞争情况,导致代码比较复杂,所以阅读的时候也要考虑全面,仔细看注释,揣摩作者的意图。
     
    • 最后,ConcurrentSkipListSet基于ConcurrentSkipListMap实现的:
    Java代码  收藏代码
    1. public class ConcurrentSkipListSet<E>  
    2.     extends AbstractSet<E>  
    3.     implements NavigableSet<E>, Cloneable, java.io.Serializable {  
    4.     private static final long serialVersionUID = -2479143111061671589L;  
    5.   
    6.     private final ConcurrentNavigableMap<E,Object> m;  
    7.   
    8.     public ConcurrentSkipListSet() {  
    9.         m = new ConcurrentSkipListMap<E,Object>();  
    10.     }  
    11.     ...  
    12.     public boolean add(E e) {  
    13.     return m.putIfAbsent(e, Boolean.TRUE) == null;  
    14.     }  
    15.     ... 
    16. 原文地址:http://brokendreams.iteye.com/blog/2253955
  • 相关阅读:
    开启防火墙如何部署k8s
    docker及k8s安装consul
    docker安装rocketmq
    docker安装gitlab
    k8s认证与授权
    部署dashboard
    k8sStatefulSet控制器
    k8sSecret资源
    k8sConfigMap资源
    使用nfs制作动态分配存储卷
  • 原文地址:https://www.cnblogs.com/prctice/p/5492018.html
Copyright © 2020-2023  润新知