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

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源码中也提供了图形化的注释:
     * Head nodes          Index nodes
     * +-+    right        +-+                      +-+
     * |2|---------------->| |--------------------->| |->null
     * +-+                 +-+                      +-+
     *  | down              |                        |
     *  v                   v                        v
     * +-+            +-+  +-+       +-+            +-+       +-+
     * |1|----------->| |->| |------>| |----------->| |------>| |->null
     * +-+            +-+  +-+       +-+            +-+       +-+
     *  v              |    |         |              |         |
     * Nodes  next     v    v         v              v         v
     * +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
     * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null
     * +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+  +-+
       可见,跳表结构中主要有3中节点:Head节点、Index节点和普通的Node节点。
 
  • 看下源码中这些节点的结构表示:
    /**
     * 节点持有key和value,按顺序链接,单向链表。
     * 中间可能会链接一些处于中间状态的标记节点。
     * 链表头节点是一个哑(dummy)节点,可以通过head.node访问。
     * value域之所以定义成Object(而不是E),是因为还要存放一些针对标记节点和头节点的特殊值(non-V)
     */
    static final class Node<K,V> {
        final K key;
        volatile Object value;
        volatile Node<K,V> next;
        /**
         * 创建一个普通节点。
         */
        Node(K key, Object value, Node<K,V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
        /**
         * 创建一个标记节点。
         * 标记节点和普通节点的重要区别是:标记节点的value域指向自身,
         * 同时标记节点的key为null。key是否为null在一些地方可以用来 
         * 区分标记节点,但无法区分标记节点和base-level链表头节点,
         * 因为base-level链表头节点的key也是null。
         */
        Node(Node<K,V> next) {
            this.key = null;
            this.value = this;
            this.next = next;
        }
        /** Updater for casNext */
        static final AtomicReferenceFieldUpdater<Node, Node>
            nextUpdater = AtomicReferenceFieldUpdater.newUpdater
            (Node.class, Node.class, "next");
        /** Updater for casValue */
        static final AtomicReferenceFieldUpdater<Node, Object>
            valueUpdater = AtomicReferenceFieldUpdater.newUpdater
            (Node.class, Object.class, "value");
        /**
         * compareAndSet value field
         */
        boolean casValue(Object cmp, Object val) {
            return valueUpdater.compareAndSet(this, cmp, val);
        }
        /**
         * compareAndSet next field
         */
        boolean casNext(Node<K,V> cmp, Node<K,V> val) {
            return nextUpdater.compareAndSet(this, cmp, val);
        }
        /**
         * 判断节点是否为标记节点。
         */
        boolean isMarker() {
            return value == this;
        }
        /**
         * 判断节点是否是base-level链表的头节点。
         */
        boolean isBaseHeader() {
            return value == BASE_HEADER;
        }
        /**
         * 尝试在当前节点后面追加一个删除标记节点。
         */
        boolean appendMarker(Node<K,V> f) {
            return casNext(f, new Node<K,V>(f));
        }
        /**
         * 通过追加一个删除标记节点或移除一个标记节点来推进删除。
         */
        void helpDelete(Node<K,V> b, Node<K,V> f) {
            /*
             * Rechecking links and then doing only one of the
             * help-out stages per call tends to minimize CAS
             * interference among helping threads.
             */
            if (f == next && this == b.next) {
                if (f == null || f.value != f) // not already marked
                    appendMarker(f);
                else
                    b.casNext(this, f.next);
            }
        }
        /**
         * 获取合法的value值。
         */
        V getValidValue() {
            Object v = value;
            if (v == this || v == BASE_HEADER)
                //如果是标记节点或者base头节点,返回null。
                return null;
            return (V)v;
        }
        /**
         * 为当前映射(Node)创建一个不变(不可修改)的快照。
         * 如果没有合法值,返回null
         */
        AbstractMap.SimpleImmutableEntry<K,V> createSnapshot() {
            V v = getValidValue();
            if (v == null)
                return null;
            return new AbstractMap.SimpleImmutableEntry<K,V>(key, v);
        }
    }

 

       上面方法中重点关注一下删除方法,删除一个节点分为两步:标记和删除。

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

 

        +------+       +------+      +------+
   ...  |   b  |------>|   n  |----->|   f  | ...
        +------+       +------+      +------+
 
      2.现在要删除节点n,那么首先要对n进行标记,如图: 

 

 

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

 

        可见,要删除节点n,首先是往节点n后面追加一个标记节点。

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

 

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

 

 

 

       上面是普通节点,再看下Index节点和Head节点:
   /**
     * Index节点表示跳表的层级。
     * 注意到Node和Index都有正向的指针,但是它们的类型和作用都不同,
     * 无法抽象到一个基类里面。
     */
    static class Index<K,V> {
        final Node<K,V> node;
        final Index<K,V> down;
        volatile Index<K,V> right;
        /**
         * Creates index node with given values.
         */
        Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
            this.node = node;
            this.down = down;
            this.right = right;
        }
        /** Updater for casRight */
        static final AtomicReferenceFieldUpdater<Index, Index>
            rightUpdater = AtomicReferenceFieldUpdater.newUpdater
            (Index.class, Index.class, "right");
        /**
         * compareAndSet right field
         */
        final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
            return rightUpdater.compareAndSet(this, cmp, val);
        }
        /**
         * 判断当前Index的Node节点是否被删除。
         */
        final boolean indexesDeletedNode() {
            return node.value == null;
        }
        /**
         * 尝试设置新的后继节点。
         */
        final boolean link(Index<K,V> succ, Index<K,V> newSucc) {
            Node<K,V> n = node;
            newSucc.right = succ;
            //需要先检测当前Index的Node是否被删除。
            return n.value != null && casRight(succ, newSucc);
        }
        /**
         * 尝试设置后继节点(right)为后继的后继(越过后继节点)
         */
        final boolean unlink(Index<K,V> succ) {
            return !indexesDeletedNode() && casRight(succ, succ.right);
        }
    }
 
    /**
     * 头节点,每个头节点都包含一个表示层级的域。
     */
    static final class HeadIndex<K,V> extends Index<K,V> {
        final int level;
        HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
            super(node, down, right);
            this.level = level;
        }
    }
 
       再看下ConcurrentSkipListMap中的结构: 
public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentNavigableMap<K,V>,
               Cloneable,
               java.io.Serializable {
   
    private static final long serialVersionUID = -8627078645895051609L;
    /**
     * 用来生成种子的随机数生成器。
     */
    private static final Random seedGenerator = new Random();
    /**
     * 用来定义base-level的头结点。
     */
    private static final Object BASE_HEADER = new Object();
    /**
     * 跳表最高层的head index
     */
    private transient volatile HeadIndex<K,V> head;
    /**
     * 比较器。如果没设置这个比较器,那么久用key的自然序来比较。
     * @serial
     */
    private final Comparator<? super K> comparator;
    /**
     * 随机种子,这里没有用volatile修饰,多个线程看到不同的值也没关系。
     */
    private transient int randomSeed;
 
 
  • 大体了解了内部结构,接下来先从简单的构造方法入手分析:

 

    public ConcurrentSkipListMap() {
        this.comparator = null;
        initialize();
    }

    public ConcurrentSkipListMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
        initialize();
    }

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

 

 

    /**
     * 初始化或重置内部状态。
     */
    final void initialize() {
        //将内部一些域置空。
        keySet = null;
        entrySet = null;
        values = null;
        descendingMap = null;
        //生成随机种子,这个种子用来生成随机的Level。
        randomSeed = seedGenerator.nextInt() | 0x0100; // 确保非0
        //生成头节点,该节点value是BASE_HEADER,level是1。
        head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
                                  null, null, 1);
    }

 

 

 

  • 然后分析下put方法:

 

    public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        return doPut(key, value, false);
    }
    private V doPut(K kkey, V value, boolean onlyIfAbsent) {
        //将原本的key转化成一个可比较的key。
        Comparable<? super K> key = comparable(kkey);
        for (;;) {
            //通过key找到要插入位置的前驱节点(注意这个节点在base_level上)
            Node<K,V> b = findPredecessor(key);
            Node<K,V> n = b.next;
            for (;;) {
                if (n != null) {
                    Node<K,V> f = n.next;
                    if (n != b.next)               //检测一下,如果读取不一致,说明发生竞争,重试。
                        break;;
                    Object v = n.value;
                    if (v == null) {               //节点n已经被删除了
                        n.helpDelete(b, f);        //删除动作
                        break;                     //重试。
                    }
                    if (v == n || b.value == null) //节点b被删除
                        break;                     //重试。
                    int c = key.compareTo(n.key);
                    if (c > 0) {
                        //如果c>0,说明当前的节点应该排在n的后面,所以从n后面继续比较。
                        b = n;
                        n = f;
                        continue;
                    }
                    if (c == 0) {
                        //如果onlyIfAbsent为true,那么不进行替换;
                        //否则需要覆盖旧值。
                        if (onlyIfAbsent || n.casValue(v, value))
                            return (V)v;
                        else
                            break; // 覆盖时竞争失败,重试。
                    }
                    // else c < 0; fall through
                }
                //1.构造一个新节点。
                Node<K,V> z = new Node<K,V>(kkey, value, n);
                //2.尝试插入b和n之间。
                if (!b.casNext(n, z))
                    break;         // 如果尝试插入失败,重试一次。
                //插入成功后,随机生成一个层级。(这个level不会超过31)
                int level = randomLevel();
                if (level > 0)
                    //level大于0,插入index
                    insertIndex(z, level);
                return null;
            }
        }
    }

 

 
        大概描述一下put方法(里面的一些细节后面分析):
              1.首先要找出当前节点(其实还没有节点,这里只是给定的key和value)在Node链表(注意这个链表是图中最下层的部分,也就是base-level)中的前驱节点。
              2.然后从前驱节点往后找到要插入的位置,进行插入。
              3.插入成功后,可能会生成一个层级。
 

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

 

    private Comparable<? super K> comparable(Object key) throws ClassCastException {
        if (key == null)
            throw new NullPointerException();
        if (comparator != null)
            return new ComparableUsingComparator<K>((K)key, comparator);
        else
            return (Comparable<? super K>)key;
    }
    static final class ComparableUsingComparator<K> implements Comparable<K> {
        final K actualKey;
        final Comparator<? super K> cmp;
        ComparableUsingComparator(K key, Comparator<? super K> cmp) {
            this.actualKey = key;
            this.cmp = cmp;
        }
        public int compareTo(K k2) {
            return cmp.compare(actualKey, k2);
        }
    }

 

       其实就是如果指定了比较器,就使用比较器;没指定比较器,就使用Key的自然序(Key需要实现Comparable接口)。
    private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) {
        // 记录下一个要添加的level,以防重试。
        int insertionLevel = indexLevel;
        Comparable<? super K> key = comparable(idx.node.key);
        if (key == null) throw new NullPointerException();
        // 和findPredecessor过程类似,只是在过程中会添加index节点。
        for (;;) {
            int j = h.level;
            Index<K,V> q = h;
            Index<K,V> r = q.right;
            Index<K,V> t = idx;
            for (;;) {
                if (r != null) {
                    Node<K,V> n = r.node;
                    // compare before deletion check avoids needing recheck
                    int c = key.compareTo(n.key);
                    if (n.value == null) {
                        if (!q.unlink(r))
                            break;
                        r = q.right;
                        continue;
                    }
                    if (c > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if (j == insertionLevel) {
                    // 这里还需要检查t节点是否被删除,如果t节点被删除,就不能插入。
                    if (t.indexesDeletedNode()) {
                        findNode(key); // cleans up
                        return;
                    }
                    //尝试将Index t插入q和q的right节点r之间。
                    if (!q.link(r, t))
                        break; // restart
                    if (--insertionLevel == 0) {
                        // 最后还要做一次删除检测。
                        if (t.indexesDeletedNode())
                            findNode(key);
                        return;
                    }
                }
                if (--j >= insertionLevel && j < indexLevel)
                    t = t.down;
                q = q.down;
                r = q.right;
            }
        }
    }
 

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

 

    /**
     * 返回最底层(base-level)节点链中比给定key小(在“给定节点”左边)的节点,
     * 如果没找到,那么返回底层链的头节点。
     * 在查找过程中会顺手删除帮助删除一点标记为删除的节点。
     */
    private Node<K,V> findPredecessor(Comparable<? super K> key) {
        if (key == null)
            throw new NullPointerException(); // don't postpone errors
        for (;;) {
            //将最高层的头节点赋给q。
            Index<K,V> q = head;
            //将最高层头结点的右节点赋给r。
            Index<K,V> r = q.right;
            for (;;) {
                if (r != null) {
                    //如果r不为null,找到r中的数据节点n。
                    Node<K,V> n = r.node;
                    K k = n.key;
                    if (n.value == null) {
                        //如果n已经被删除,那么尝试推进删除。
                        if (!q.unlink(r))
                            break;           // 推进删除失败,重试。
                        r = q.right;         // 再次读取q的头结点,因为上面删除成功后,q的右节点变了。
                        continue;
                    }
                    //n没被删除的话,和key进行比较。
                    if (key.compareTo(k) > 0) {
                        //如果给定的key表示的节点在n后面的话,继续往后找。
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                //如果r为null,那么往下找。
                //获取q的下节点d
                Index<K,V> d = q.down;
                if (d != null) {
                    //如果d不为null,将d赋给q,d的右节点赋给r,再次循环。
                    q = d;
                    r = d.right;
                } else
                    return q.node; //如果d为空,说明q就是最底层的节点,返回这个节点。
            }
        }
    }

 

       上述过程其实可以简单的理解为,从最高层的头节点开始找,给定key大于当前节点,就往右找,否则就往下找,一直找到最底层链上的节点,这个节点就是给定key在base_level上的前驱节点。
 

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

 

    /**
     * Returns a random level for inserting a new node.
     * Hardwired to k=1, p=0.5, max 31 (see above and
     * Pugh's "Skip List Cookbook", sec 3.4).
     *
     * This uses the simplest of the generators described in George
     * Marsaglia's "Xorshift RNGs" paper.  This is not a high-quality
     * generator but is acceptable here.
     */
    private int randomLevel() {
        int x = randomSeed;
        x ^= x << 13;
        x ^= x >>> 17;
        randomSeed = x ^= x << 5;
        if ((x & 0x8001) != 0) // test highest and lowest bits
            return 0;
        int level = 1;
        while (((x >>>= 1) & 1) != 0) ++level;
        return level;
    }

 

       这段代码有些蛋疼...总之就是50%的几率返回0,25%的几率返回1,12.5%的几率返回2...最大返回31。

       继续看insertIndex方法:

 

    /**
     * 为给定的数据节点创建和添加Index节点。
     */
    private void insertIndex(Node<K,V> z, int level) {
        HeadIndex<K,V> h = head;
        int max = h.level;
        if (level <= max) {
            Index<K,V> idx = null;
            //如果level比当前的max level小,那么创建level个节点,纵向链接起来
            //(level2的down节点指向level1、level3的down节点指向level2...)
            for (int i = 1; i <= level; ++i)
                idx = new Index<K,V>(z, idx, null);
            //添加index。
            addIndex(idx, h, level);
        } else { //如果level比当前的max level大,添加level。
            /*
             * 为了减小其他线程在tryReduceLevel方法中检测空level的干扰,
             * 新的level添加时右节点就已经初始化好了。它们被依次放到一个
             * 数组里面,当创建新的head index时,会反向访问它们。
             */
            //level设置为max+1
            level = max + 1;
            //建立一个长度为level的Index数组,
            Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1];
            Index<K,V> idx = null;
            //还是创建level个节点,纵向链接起来,同时将它们放入Index数组。
            for (int i = 1; i <= level; ++i)
                idxs[i] = idx = new Index<K,V>(z, idx, null);
            HeadIndex<K,V> oldh;
            int k;
            for (;;) {
                oldh = head;
                int oldLevel = oldh.level;
                if (level <= oldLevel) { // 竞争失败,跳出。
                    k = level;
                    break;
                }
                HeadIndex<K,V> newh = oldh;
                Node<K,V> oldbase = oldh.node;
                for (int j = oldLevel+1; j <= level; ++j)
                    /*
                     *这里创建新的HeadIndex,其数据节点为oldBase,down节点为
                     *之前的head,right节点为上面Index中level最高的节点,level为j
                     */
                    newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                //尝试将head设置为新创建的HeadIndex。
                if (casHead(oldh, newh)) {
                    k = oldLevel;
                    break;
                }
            }
            //添加index。
            addIndex(idxs[k], oldh, k);
        }
    }

  

 

 

       再继续看下这个addIndex方法:

    private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) {
        // 记录下一个要添加的level,以防重试。
        int insertionLevel = indexLevel;
        Comparable<? super K> key = comparable(idx.node.key);
        if (key == null) throw new NullPointerException();
        // 和findPredecessor过程类似,只是在过程中会添加index节点。
        for (;;) {
            int j = h.level;
            Index<K,V> q = h;
            Index<K,V> r = q.right;
            Index<K,V> t = idx;
            for (;;) {
                if (r != null) {
                    Node<K,V> n = r.node;
                    // compare before deletion check avoids needing recheck
                    int c = key.compareTo(n.key);
                    if (n.value == null) {
                        if (!q.unlink(r))
                            break;
                        r = q.right;
                        continue;
                    }
                    if (c > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if (j == insertionLevel) {
                    // 这里还需要检查t节点是否被删除,如果t节点被删除,就不能插入。
                    if (t.indexesDeletedNode()) {
                        findNode(key); // cleans up
                        return;
                    }
                    //尝试将Index t插入q和q的right节点r之间。
                    if (!q.link(r, t))
                        break; // restart
                    if (--insertionLevel == 0) {
                        // 最后还要做一次删除检测。
                        if (t.indexesDeletedNode())
                            findNode(key);
                        return;
                    }
                }
                if (--j >= insertionLevel && j < indexLevel)
                    t = t.down;
                q = q.down;
                r = q.right;
            }
        }
    }

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

 
              

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

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

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

    private V getUsingFindNode(Comparable<? super K> key) {
        for (;;) {
            Node<K,V> n = findNode(key);
            if (n == null)
                return null;
            Object v = n.value;
            if (v != null)
                return (V)v;
        }
    }
       此方法中会再次调用前面分析过的findNode方法来查找一个key对应的Node,注意方法中外侧还是包了一层无限循环,为的是避免由于竞争导致findNode方法返回的Node又是一个被删除的节点。
 
  • 继续看下containsKey方法:
    public boolean containsKey(Object key) {
        return doGet(key) != null;
    }
       containsKey实现非常简单,直接基于doGet方法来做。
 
  • 接着看下remove方法:
    public V remove(Object key) {
        return doRemove(key, null);
    }
    final V doRemove(Object okey, Object value) {
        //转换成可比较的key
        Comparable<? super K> key = comparable(okey);
        for (;;) {
            //找到key在base_level链上的前驱节点。
            Node<K,V> b = findPredecessor(key);
            Node<K,V> n = b.next;
            for (;;) {
                if (n == null)
                    return null;
                Node<K,V> f = n.next;
                if (n != b.next)                    // 读取不一致,重试。
                    break;
                Object v = n.value;
                if (v == null) {                    // 如果n被删除了,帮助推进删除,然后重试。
                    n.helpDelete(b, f);
                    break;
                }
                if (v == n || b.value == null)      // 如果b被删除了,重试。
                    break;
                int c = key.compareTo(n.key);
                if (c < 0)
                    return null; //如果比找到的前驱节点的后继节点小,说明没有指定的key对应的节点,返回null。
                if (c > 0) {
                    //如果比找到的前驱节点的后继节点大,说明目标节点在这个节点后面,往后找。
                    b = n;
                    n = f;
                    continue;
                }
                if (value != null && !value.equals(v))
                    return null;//给定的value和链表中的value不一致,删除失败。
                if (!n.casValue(v, null))//首先尝试将要删除的目标节点n的value置空。
                    break; //如果失败,说明发生竞争,重试。
                /*
                 * 如果上一步将n的value置空成功,接下来首先尝试将n的后面追加一个标记节点,
                 * 成功的话,再尝试将n和标记节点一起移除,这两部有任何一步失败,都会调用
                 * findNode来完成删除(利用findNode方法的副作用)
                 */
                if (!n.appendMarker(f) || !b.casNext(n, f))
                    findNode(key);                  // Retry via findNode
                else {
                    /*
                     * 如果上面的n.appendMarker(f)和b.casNext(n, f)都调用成功,
                     * 然后就会调用这个方法,注意这里其实也是使用这个方法的
                     * 副作用来删除节点n的Index节点。
                     */
                    findPredecessor(key);           
                    if (head.right == null)
                        // 删除了一些Index之后,这里判断一下head的right节点是否为null,
                        // 如果为null,说明最高层的Index链已经不存在数据,可以删掉了。
                        tryReduceLevel();
                }
                return (V)v;
            }
        }
    }

 

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

    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
    }
       这个方法看起来有点绕,其实是说:如果最高的前三个HeadIndex都为null(当前看起来),那么就将level减少1层,其实就是将h(当前的head)设置为d(head的下一层),设置完成之后,还有检测h(之前的head)的right是否为null(因为可能刚才由于竞争的原因,导致看到h的right为null),如果这会儿又不是null,那么还得回退回来,再次将head设置为h。
 
  • 再看下containsValue方法:
    public boolean containsValue(Object value) {
        if (value == null)
            throw new NullPointerException();
        for (Node<K,V> n = findFirst(); n != null; n = n.next) {
            V v = n.getValidValue();
            if (v != null && value.equals(v))
                return true;
        }
        return false;
    }
       containsValue的实现也比较简答,就是先找到base_level链的第一个节点,然后一直往后找,比较value值。

       看下上面用到的findFirst方法: 

    Node<K,V> findFirst() {
        for (;;) {
            Node<K,V> b = head.node;
            Node<K,V> n = b.next;
            if (n == null)
                return null;
            if (n.value != null)
                return n;
            n.helpDelete(b, n.next);
        }
    }

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

    public int size() {
        long count = 0;
        for (Node<K,V> n = findFirst(); n != null; n = n.next) {
            if (n.getValidValue() != null)
                ++count;
        }
        return (count >= Integer.MAX_VALUE)? Integer.MAX_VALUE : (int)count;
    }
    public boolean isEmpty() {
        return findFirst() == null;
    }

 

  • ConcurrentSkipListMap实现了ConcurrentMap接口,看下这些接口方法的实现:
   public V putIfAbsent(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        return doPut(key, value, true);
    }

    public boolean remove(Object key, Object value) {
        if (key == null)
            throw new NullPointerException();
        if (value == null)
            return false;
        return doRemove(key, value) != null;
    }

    public boolean replace(K key, V oldValue, V newValue) {
        if (oldValue == null || newValue == null)
            throw new NullPointerException();
        Comparable<? super K> k = comparable(key);
        for (;;) {
            Node<K,V> n = findNode(k);
            if (n == null)
                return false;
            Object v = n.value;
            if (v != null) {
                if (!oldValue.equals(v))
                    return false;
                if (n.casValue(v, newValue))
                    return true;
            }
        }
    }

    public V replace(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        Comparable<? super K> k = comparable(key);
        for (;;) {
            Node<K,V> n = findNode(k);
            if (n == null)
                return null;
            Object v = n.value;
            if (v != null && n.casValue(v, value))
                return (V)v;
        }
    }
       ConcurrentMap API方法实现中使用到的方法前面都分析过了,可以参考下前面的分析。
 
  • ConcurrentSkipListMap同样实现了SortedMap接口,看下相关接口方法的实现:
    public K lastKey() {
        Node<K,V> n = findLast();
        if (n == null)
            throw new NoSuchElementException();
        return n.key;
    }

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

    Node<K,V> findLast() {
        Index<K,V> q = head;
        for (;;) {
            Index<K,V> d, r;
            if ((r = q.right) != null) {
                if (r.indexesDeletedNode()) { //如果发现节点已经删除的Index,顺便移除。
                    q.unlink(r);
                    q = head; // 重试。
                }
                else
                    q = r; //向右找
            } else if ((d = q.down) != null) {
                q = d; //向下找
            } else {
                //现在到了base_level链上,向后找。
                Node<K,V> b = q.node;
                Node<K,V> n = b.next;
                for (;;) {
                    if (n == null)
                        //最后定位到节点后需要检测一下是不是baseHead
                        return (b.isBaseHeader())? null : b; 
                    Node<K,V> f = n.next;            // 读取不一致,有竞争发生,重试。
                    if (n != b.next)
                        break;
                    Object v = n.value;
                    if (v == null) {                 // n节点被删除,重试。
                        n.helpDelete(b, f);
                        break;
                    }
                    if (v == n || b.value == null)   // b节点被删除,重试。
                        break;
                    b = n;
                    n = f;
                }
                q = head; // restart
            }
        }
    }

 

       再看一个方法:
    public Map.Entry<K,V> lowerEntry(K key) {
        return getNear(key, LT);
    }
       这个方法的意思是找到一个比给定key小的所有key里面最大的key对应的Entry,里面调用了getNear方法: 
    AbstractMap.SimpleImmutableEntry<K,V> getNear(K key, int rel) {
        for (;;) {
            Node<K,V> n = findNear(key, rel);
            if (n == null)
                return null;
            AbstractMap.SimpleImmutableEntry<K,V> e = n.createSnapshot();
            if (e != null)
                return e;
        }
    }
       getNear方法里面首先通过findNear方法找到指定的Node,然后通过createSnapshot方法返回一个Entry,这个方法最开始的时候看到过。下面重点看下这个findNear方法: 
    private static final int EQ = 1;
    private static final int LT = 2;
    private static final int GT = 0; // Actually checked as !LT
    /**
     * Utility for ceiling, floor, lower, higher methods.
     * @param kkey the key
     * @param rel the relation -- OR'ed combination of EQ, LT, GT
     * @return nearest node fitting relation, or null if no such
     */
    Node<K,V> findNear(K kkey, int rel) {
        Comparable<? super K> key = comparable(kkey);
        for (;;) {
            Node<K,V> b = findPredecessor(key);
            Node<K,V> n = b.next;
            for (;;) {
                if (n == null)
                    return ((rel & LT) == 0 || b.isBaseHeader())? null : b; //出口1
                Node<K,V> f = n.next;
                if (n != b.next)                  // inconsistent read
                    break;
                Object v = n.value;
                if (v == null) {                  // n is deleted
                    n.helpDelete(b, f);
                    break;
                }
                if (v == n || b.value == null)    // b is deleted
                    break;
                int c = key.compareTo(n.key);
                if ((c == 0 && (rel & EQ) != 0) ||
                    (c <  0 && (rel & LT) == 0))
                    return n; //出口2
                if ( c <= 0 && (rel & LT) != 0)
                    return (b.isBaseHeader())? null : b; //出口3
                b = n;
                n = f;
            }
        }
    }
       这个方法整体流程其实和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: 
    public Map.Entry<K,V> floorEntry(K key) {
        return getNear(key, LT|EQ);
    }
       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:
    public Map.Entry<K,V> ceilingEntry(K key) {
        return getNear(key, GT|EQ);
    }
       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: 
    public Map.Entry<K,V> higherEntry(K key) {
        return getNear(key, GT);
    }
       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实现的:
public class ConcurrentSkipListSet<E>
    extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = -2479143111061671589L;

    private final ConcurrentNavigableMap<E,Object> m;

    public ConcurrentSkipListSet() {
        m = new ConcurrentSkipListMap<E,Object>();
    }
    ...
    public boolean add(E e) {
	return m.putIfAbsent(e, Boolean.TRUE) == null;
    }
    ...
       方法实现,都由内部的ConcurrentSkipListMap代理实现,value默认填充Boolean.TRUE。
 
 

       ok,代码解析完毕! 

 

 

 

 

 
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值