ConcurrentSkipListMap

特性

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable {}

public interface ConcurrentNavigableMap<K,V>
    extends ConcurrentMap<K,V>, NavigableMap<K,V>
{}

ConcurrentNavigableMap:
ConcurrentNavigableMap接口就很简单了,从定义上看,它继承了NavigableMap和ConcurrentMap这两个接口子Map,就是两者功能的结合,既保证线程安全性,又提供导航搜索子Map视图的功能。ConcurrentNavigableMap中定义的方法也很简单,跟NavigableMap中的方法类似,只不过NavigableMap中返回的子Map视图是NavigableMap类型,而在ConcurrentNavigableMap中返回的都是ConcurrentNavigableMap类型

基本属性

/**
     * 最底层链表的头指针BASE_HEADER
     */
    private static final Object BASE_HEADER = new Object();

    /**
     * 最上层链表的头指针head
     */
    private transient volatile HeadIndex<K, V> head;

    /* ---------------- 普通结点Node定义 -------------- */
    static final class Node<K, V> {
        final K key;
        volatile Object value;
        volatile Node<K, V> next;

        // ...
    }

    /* ---------------- 索引结点Index定义 -------------- */
    static class Index<K, V> {
        final Node<K, V> node;      // node指向最底层链表的Node结点
        final Index<K, V> down;     // down指向下层Index结点
        volatile Index<K, V> right; // right指向右边的Index结点

        // ...
    }

    /* ---------------- 头索引结点HeadIndex -------------- */
    static final class HeadIndex<K, V> extends Index<K, V> {
        final int level;    // 层级

        // ...
    }

普通结点:Node
普通结点——Node,也就是ConcurrentSkipListMap最底层链表中的结点,保存着实际的键值对,如果单独看底层链,其实就是一个按照Key有序排列的单链表:

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;
    }

    /**
     * 标记结点.
     */
    Node(Node<K, V> next) {
        this.key = null;
        this.value = this;
        this.next = next;
    }

    /**
     * CAS更新结点的value
     */
    boolean casValue(Object cmp, Object val) {
        return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
    }

    /**
     * CAS更新结点的next
     */
    boolean casNext(Node<K, V> cmp, Node<K, V> val) {
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    /**
     * 判断当前结点是否为[标记结点]
     */
    boolean isMarker() {
        return value == this;
    }

    /**
     * 判断当前结点是否是最底层链表的头结点
     */
    boolean isBaseHeader() {
        return value == BASE_HEADER;
    }

    /**
     * 在当前结点后面插入一个标记结点.
     *
     * @param f 当前结点的后继结点
     * @return true 插入成功
     */
    boolean appendMarker(Node<K, V> f) {
        return casNext(f, new Node<K, V>(f));
    }

    /**
     * 辅助删除结点方法.
     *
     * @param b 当前结点的前驱结点
     * @param f 当前结点的后继结点
     */
    void helpDelete(Node<K, V> b, Node<K, V> f) {
        /*
         * 重新检查一遍结点位置
         * 确保b和f分别为当前结点的前驱/后继
         */
        if (f == next && this == b.next) {
            if (f == null || f.value != f)  // f为null或非标记结点
                casNext(f, new Node<K, V>(f));
            else                            // 删除当前结点
                b.casNext(this, f.next);
        }
    }

    /**
     * 返回结点的value值.
     *
     * @return 标记结点或最底层头结点,直接返回null
     */
    V getValidValue() {
        Object v = value;
        if (v == this || v == BASE_HEADER)  // 标记结点或最底层头结点,直接返回null
            return null;
        V vv = (V) v;
        return vv;
    }

    /**
     * 返回当前结点的一个Immutable快照.
     */
    AbstractMap.SimpleImmutableEntry<K, V> createSnapshot() {
        Object v = value;
        if (v == null || v == this || v == BASE_HEADER)
            return null;
        V vv = (V) v;
        return new AbstractMap.SimpleImmutableEntry<K, V>(key, vv);
    }

    // UNSAFE mechanics

    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    private static final long nextOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = Node.class;
            valueOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("value"));
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

索引结点:Index
Index结点是除底层链外,其余各层链表中的非头结点(见示意图中的蓝色结点)。每个Index结点包含3个指针:down、right、node。
down和right指针分别指向下层结点和后继结点,node指针指向其最底部的node结点。

static class Index<K, V> {
    final Node<K, V> node;      // node指向最底层链表的Node结点
    final Index<K, V> down;     // down指向下层Index结点
    volatile Index<K, V> right; // right指向右边的Index结点

    Index(Node<K, V> node, Index<K, V> down, Index<K, V> right) {
        this.node = node;
        this.down = down;
        this.right = right;
    }

    /**
     * CAS更新右边的Index结点
     *
     * @param cmp 当前结点的右结点
     * @param val 希望更新的结点
     */
    final boolean casRight(Index<K, V> cmp, Index<K, V> val) {
        return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
    }

    /**
     * 判断Node结点是否已经删除.
     */
    final boolean indexesDeletedNode() {
        return node.value == null;
    }

    /**
     * CAS插入一个右边结点newSucc.
     *
     * @param succ    当前的后继结点
     * @param newSucc 新的后继结点
     */
    final boolean link(Index<K, V> succ, Index<K, V> newSucc) {
        Node<K, V> n = node;
        newSucc.right = succ;
        return n.value != null && casRight(succ, newSucc);
    }

    /**
     * 跳过当前结点的后继结点.
     *
     * @param succ 当前的后继结点
     */
    final boolean unlink(Index<K, V> succ) {
        return node.value != null && casRight(succ, succ.right);
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long rightOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = Index.class;
            rightOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("right"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

头索引结点:HeadIndex
HeadIndex结点是各层链表的头结点,它是Index类的子类,唯一的区别是增加了一个level字段,用于表示当前链表的级别,越往上层,level值越大。

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;
    }
}

构造方法

/**
 * 构造一个新的空Map.
 */
public ConcurrentSkipListMap() {
    this.comparator = null;
    initialize();
}

/**
 * 构造一个新的空Map.
 * 并指定比较器.
 */
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
    initialize();
}

/**
 * 从已给定的Map构造一个新Map.
 */
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
    this.comparator = null;
    initialize();
    putAll(m);
}

/**
 * 从已给定的SortedMap构造一个新Map.
 * 并且Key的顺序与原来保持一致.
 */
public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
    this.comparator = m.comparator();
    initialize();
    buildFromSorted(m);
}

上述所有构造器都调用了initialize方法:

private void initialize() {
    keySet = null;
    entrySet = null;
    values = null;
    descendingMap = null;
    head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null),null, null, 1);
}

initialize方法将一些字段置初始化null,然后将head指针指向新创建的HeadIndex结点。初始化完成后,ConcurrentSkipListMap的结构如下:

在这里插入图片描述

数据结构特点

  • 什么是Skip List
    Skip List(以下简称跳表),是一种类似链表的数据结构,其查询/插入/删除的时间复杂度都是O(logn)。
    我们知道,通常意义上的链表是不能支持随机访问的(通过索引快速定位),其查找的时间复杂度是O(n),而数组这一可支持随机访问的数据结构,虽然查找很快,但是插入/删除元素却需要移动插入点后的所有元素,时间复杂度为O(n)。
    为了解决这一问题,引入了树结构,树的增删改查效率比较平均,一棵平衡二叉树(AVL)的增删改查效率一般为O(logn),比如工业上常用红黑树作为AVL的一种实现。
    但是,AVL的实现一般都比较复杂,插入/删除元素可能涉及对整个树结构的修改,特别是并发环境下,通常需要全局锁来保证AVL的线程安全,于是又出现了一种类似链表的数据结构——跳表。

  • Skip List示例

在讲Skip List之前,我们先来看下传统的单链表:
在这里插入图片描述

上图的单链表中(省去了结点之间的链接),当想查找7、15、46这三个元素时,必须从头指针head开始,遍历整个单链表,其查找复杂度很低,为O(n)。

来看下Skip List的数据结构是什么样的:
在这里插入图片描述
上图是Skip List一种可能的结构,它分了2层,假设我们要查找“15”这个元素,那么整个步骤如下:

  1. 从头指针head开始,找到第一个结点的最上层,发现其指向的下个结点值为8,小于15,则直接从1结点跳到8结点。
  2. 8结点最上层指向的下一结点值为18,大于15,则从8结点的下一层开始查找。
  3. 从8结点的最下层一直向后查找,依次经过10、13,最后找到15结点。

上述整个查找路径如下图标黄部分所示:

在这里插入图片描述
同理,如果要查找“46”这个元素,则整个查找路径如下图标黄部分所示:
在这里插入图片描述
上面就是跳跃表的基本思想了,每个结点不仅仅只包含指向下一个结点的指针,可能还包含很多个其它指向后续结点的指针。并且,一个结点本身可以看成是一个链表(自上向下链接)。这样就可以跳过一些不必要的结点,从而加快查找、删除等操作,这其实是一种“空间换时间”的算法设计思想。

那么一个结点可以包含多少层呢? 比如,Skip List也可能是下面这种包含3层的结构(在一个3层Skip List中查找元素“46”)
在这里插入图片描述
层数是根据一种随机算法得到的,为了不让层数过大,还会有一个最大层数MAX_LEVEL限制,随机算法生成的层数不得大于该值。

以上就是Skip List的基本思想了,总结起来,有以下几点:

  1. 跳表由很多层组成;
  2. 每一层都是一个有序链表;
  3. 对于每一层的任意结点,不仅有指向下一个结点的指针,也有指向其下一层的指针。

添加数据

put操作本身很简单,需要注意的是ConcurrentSkipListMap在插入键值对时,Key和Value都不能为null:

/**
 * 插入键值对.
 *
 * @param key   键
 * @param value 值
 * @return 如果key存在,返回旧value值;否则返回null
 */
public V put(K key, V value) {
    if (value == null)          // ConcurrentSkipListMap的Value不能为null
        throw new NullPointerException();
    return doPut(key, value, false);
}
/**
 * 插入键值对.
 *
 * @param onlyIfAbsent true: 仅当Key不存在时才进行插入
 */
private V doPut(K key, V value, boolean onlyIfAbsent) {
    Node<K, V> z;             // z指向待添加的Node结点
    if (key == null)          // ConcurrentSkipListMap的Key不能为null
        throw new NullPointerException();

    Comparator<? super K> cmp = comparator;
    outer:
    for (; ; ) {
        // b是“是小于且最接近给定key”的Node结点(或底层链表头结点)
        for (Node<K, V> b = findPredecessor(key, cmp), n = b.next; ; ) {
            if (n != null) {                    // b存在后驱结点:  b -> n -> f
                Object v;
                int c;
                Node<K, V> f = n.next;          // f指向b的后驱的后驱
                if (n != b.next)                // 存在并发修改,放弃并重试
                    break;
                if ((v = n.value) == null) {    // n为标记删除结点
                    n.helpDelete(b, f);
                    break;
                }
                if (b.value == null || v == n)  // b为标记删除结点
                    break;
                if ((c = cpr(cmp, key, n.key)) > 0) {   // 向后遍历,找到第一个大于key的结点
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) {                           // 存在Key相同的结点
                    if (onlyIfAbsent || n.casValue(v, value)) {
                        V vv = (V) v;
                        return vv;
                    }
                    break; // CAS更新失败,则重试
                }
            }

            z = new Node<K, V>(key, value, n);
            if (!b.casNext(n, z))  // 尝试插入z结点: b -> z -> n
                break;         // CAS插入失败,则重试
            break outer;           // 跳出最外层循环
        }
    }

    int rnd = ThreadLocalRandom.nextSecondarySeed();    // 生成一个随机数种子
    if ((rnd & 0x80000001) == 0) {                      // 为true表示需要增加层级

        /**
         * 以下方法用于创建新层级
         */
        int level = 1, max;
        while (((rnd >>>= 1) & 1) != 0) // level表示新的层级,通过下面这个while循环可以确认新的层级数
            ++level;

        Index<K, V> idx = null;
        HeadIndex<K, V> h = head;

        if (level <= (max = h.level)) {         // CASE1: 新层级level没有超过最大层级head.level(head指针指向最高层)
            // 以“头插法”创建level个Index结点,idx最终指向最高层的Index结点
            for (int i = 1; i <= level; ++i)
                idx = new Index<K, V>(z, idx, null);
        }
        else {                                  // CASE2: 新层级level超过了最大层级head.level
            level = max + 1;    // 重置level为最大层级+1

            // 生成一个Index结点数组,idxs[0]不会使用
            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);

            // 生成新的HeadIndex结点
            for (; ; ) {
                h = head;
                int oldLevel = h.level;         // 原最大层级
                if (level <= oldLevel)
                    break;
                HeadIndex<K, V> newh = h;
                Node<K, V> oldbase = h.node;    // oldbase指向最底层链表的头结点
                for (int j = oldLevel + 1; j <= level; ++j)
                    newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j);
                if (casHead(h, newh)) {
                    h = newh;
                    idx = idxs[level = oldLevel];
                    break;
                }
            }
        }

        /**
         * 以下方法用于链接新层级的各个HeadIndex和Index结点
         */
        splice:
        for (int insertionLevel = level; ; ) {  // 此时level为oldLevel,即原最大层级
            int j = h.level;
            for (Index<K, V> q = h, r = q.right, t = idx; ; ) {
                if (q == null || t == null)
                    break splice;
                if (r != null) {
                    Node<K, V> n = r.node;

                    int c = cpr(cmp, key, 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) {
                    if (!q.link(r, t))      // 在q和r之间插入t,即从 q -> r 变成 q -> t -> r
                        break;
                    if (t.node.value == null) {
                        findNode(key);
                        break splice;
                    }
                    if (--insertionLevel == 0)
                        break splice;
                }

                if (--j >= insertionLevel && j < level)
                    t = t.down;
                q = q.down;
                r = q.right;
            }
        }
    }
    return null;
}

在这里插入图片描述
同时根据int rnd = ThreadLocalRandom.nextSecondarySeed(); if ((rnd & 0x80000001) == 0) 判断要不要增加层级

获取数据

  1. 寻找 key 的前继节点 b (这时b.next = null || b.next > key, 则说明不存key对应的 Node)
  2. 接着就判断 b, b.next 与 key之间的关系(其中有些 helpDelete操作)
/**
 * Gets value for key. Almost the same as findNode, but returns
 * the found value (to avoid retires during ret-reads)
 *
 *  这个 doGet 方法比较简单
 * @param key the key
 * @return the value, or null if absent
 */
private V doGet(Object key){
    if(key == null){
        throw new NullPointerException();
    }
    Comparator<? super K> cmp = comparator;
    outer:
    for(;;){
        for(Node<K, V> b = findPredecessor(key, cmp), n = b.next;;){ // 1. 获取 key 的前继节点 b, 其实这时 n.key >= key
            Object v; int c;
            if(n == null){ // 2. n == null 说明 key 对应的 node 不存在 所以直接 return null
                break outer;
            }
            Node<K, V> f = n.next;
            if(n != b.next){ // 3. 有另外的线程修改数据, 重新来
                break ;
            }
            if((v = n.value) == null){ // 4. n 是被删除了的节点, 进行helpDelete 后重新再来
                n.helpDelete(b, f);
                break ;
            }
            if(b.value == null || v == n){ // 5. b已经是删除了的节点, 则 break 后再来
                break ;
            }
            if((c = cpr(cmp, key, n.key)) == 0){ // 6. 若 n.key = key 直接返回结果, 这里返回的结果有可能是 null
                V vv = (V) v;
                return vv;
            }
            if(c < 0){ // 7. c < 0说明不存在 key 的node 节点
                break outer;
            }
            // 8. 运行到这一步时, 其实是 在调用 findPredecessor 后又有节点添加到 节点b的后面所致
            b = n;
            n = f;
        }
    }

    return null;
}

删除数据

ConcurrentSkipListMap在删除键值对时,不会立即执行删除,而是通过引入“标记结点”,以“懒删除”的方式进行,以提高并发效率。

/**
 * Main deletion method. Locates node, nulls value, appends a
 * deletion marker, unlinks predecessor, removes associated index
 * nodes, and possibly reduces head index level
 *
 * Index nodes are cleared out simply by calling findPredecessor.
 * which unlinks indexes to deleted nodes found along path to key,
 * which will include the indexes to this node. This is node
 * unconditionally. We can't check beforehand whether there are
 * indexes hadn't been inserted yet for this node during initial
 * search for it, and we'd like to ensure lack of garbage
 * retention, so must call to be sure
 *
 * @param key the key
 * @param value if non-null, the value that must be
 *              associated with key
 * @return the node, or null if not found
 */
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;;){ // 1. 获取对应的前继节点 b
            Object v; int c;
            if(n == null){ // 2. 节点 n 被删除 直接 return null 返回 , 因为理论上 b.key < key < n.key
                break outer;
            }
            Node<K, V> f = n.next;
            if(n != b.next){ // 3. 有其他线程在 节点b 后增加数据, 重来
                break ;
            }
            if((v = n.value) == null){ // 4. 节点 n 被删除, 调用 helpDelete 后重来
                n.helpDelete(b, f);
                break ;
            }

            if(b.value == null || v == n){ // 5. 节点 b 删除, 重来 调用findPredecessor时会对 b节点对应的index进行清除, 而b借点吧本身会通过 helpDelete 来删除
                break ;
            }
            if((c = cpr(cmp, key, n.key)) < 0){ // 6. 若n.key < key 则说明 key 对应的节点就不存在, 所以直接 return
                break outer;
            }

            if(c > 0){ // 7. c>0 出现在 有其他线程在本方法调用findPredecessor后又在b 后增加节点, 所以向后遍历
                b = n;
                n = f;
                continue ;
            }

            if(value != null && !value.equals(v)){ // 8. 若 前面的条件为真, 则不进行删除 (调用 doRemove 时指定一定要满足 key value 都相同, 具体看 remove 方法)
                break outer;
            }
            if(!n.casValue(v, null)){ // 9. 进行数据的删除
                break ;
            }
            if(!n.appendMarker(f) || !b.casNext(n, f)){ // 10. 进行 marker 节点的追加, 这里的第二个 cas 不一定会成功, 但没关系的 (第二个 cas 是删除 n节点, 不成功会有  helpDelete 进行删除)
                findNode(key);  // 11. 对 key 对应的index 进行删除
            }
            else{
                findPredecessor(key, cmp); //12. 对 key 对应的index 进行删除 10进行操作失败后通过 findPredecessor 进行index 的删除
                if(head.right == null){
                    tryReduceLevel(); // 13. 进行headIndex 对应的index 层的删除
                }
            }

            V vv = (V) v;
            return vv;

        }
    }

    return null;
}

假设现在要删除Key23的结点,删除前ConcurrentSkipListMap的结构如下:
在这里插入图片描述
doRemove方法首先会找到待删除的结点,在它和后继结点之间插入一个value为null的标记结点(如下图中的绿色结点),然后改变其前驱结点的指向:
在这里插入图片描述
最后,doRemove会重新调用一遍findPredecessor方法,解除被删除结点上的Index结点之间的引用:
在这里插入图片描述
这样Key
23的结点其实就被孤立,再后续查找或插入过程中,会被完全清除或被GC回收。

总结

ConcurrentSkipListMap 是一个基于 Skip List 实现的并发安全, 非阻塞读/写/删除 的 Map, 最重要的是 它的value是有序存储的, 而且其内部是由纵横链表组成

参考

https://www.cnblogs.com/java-zzl/p/9767255.html
https://www.jianshu.com/p/edc2fd149255
https://segmentfault.com/a/1190000016168566?utm_source=tag-newest

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值