关闭

JUC源码分析16-集合-ConcurrentSkipListMap、ConcurrentSkipListSet

标签: JUC
1364人阅读 评论(0) 收藏 举报
分类:

NBA这赛季结束,勇士可惜啊,谁能想到没拿到冠军,库昊也没成为真正的老大,lbl一战封神,所有口水留言都变成羡慕嫉妒恨,哎,我库啊,还是还是看书吧。

ConcurrentSkipListMap说实话,之前还真没注意过,还是看JUC才看到,利用skiplist跳表结构来实现一种有序的map,之前看到的map都是无序。在学习前还是要好好了解下什么是skiplist跳表,的确很不错,利用空间换时间,复杂度为logN,跳表的原理参考http://kenby.iteye.com/blog/1187303,讲的不错,一定要多看几遍,理解不了skiplist,看这个类估计也勉强。

ConcurrentSkipListMap的特点:1.线程安全有序map;2.复杂度logN;3.无锁的map,所以并发量应该比之前的map好些,具体没测。

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种节点:Node,Index,HeadIndex,过会再看,还有个东西:

     * Notation guide for local variables
     * Node:         b, n, f    for  predecessor, node, successor
     * Index:        q, r, d    for index node, right, down.
     *               t          for another index node
     * Head:         h
     * Levels:       j
     * Keys:         k, key
     * Values:       v, value
     * Comparisons:  c

这是类中一些变量的说明,因为用的是lock-free list,所以在做各种操作时n多检查,看起来真累。

看下3中节点,Node节点先:

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指向自己.主要是删除一个节点的时候会在该节点后面追加一个标记节点
     */
    Node(Node<K,V> next) {
        this.key = null;
        this.value = this;
        this.next = next;
    }
    /**
     * compareAndSet value field
     */
    boolean casValue(Object cmp, Object val) {
        return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
    }
    /**
     * compareAndSet next field
     */
    boolean casNext(Node<K,V> cmp, Node<K,V> val) {
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    /**
     * 是否标记节点,没看到地方用这个
     */
    boolean isMarker() {
        return value == this;
    }
    /**
     * 最底层有序list的头结点
	 * Object BASE_HEADER = new Object()
     */
    boolean isBaseHeader() {
        return value == BASE_HEADER;
    }
    /**
     * 当前节点的后面追加一个标记节点,删除的时候会把当前节点和标记节点一起删除
     */
    boolean appendMarker(Node<K,V> f) {
        return casNext(f, new Node<K,V>(f));
    }
    /**
     * 是否还记得b,n,f变量,b是当前节点的前驱,n是当前节点,f是后一个节点
	 * 帮助删除方法?就是如果当前节点后面已经有标记节点那就casnext替换删除,如果没有那就追加一个标记节点
     */
    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) // 当前节点后面一个节点不是标记,那就追加一个标记
                appendMarker(f);
            else
                b.casNext(this, f.next);
        }
    }
    /**
     * 返回节点的value,如果头结点或标记节点返回null
     */
    V getValidValue() {
        Object v = value;
        if (v == this || v == BASE_HEADER)
            return null;
        return (V)v;
    }
    /**
     * Creates and returns a new SimpleImmutableEntry holding current
     * mapping if this node holds a valid value, else null.
     * @return new entry or null
     */
    AbstractMap.SimpleImmutableEntry<K,V> createSnapshot() {
        V v = getValidValue();
        if (v == null)
            return null;
        return new AbstractMap.SimpleImmutableEntry<K,V>(key, v);
    }
    // UNSAFE的一些,看起来应该不会陌生了
    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);
        }
    }
}
最重要的应该是helpDelete方法吧。javadoc里面有个解释删除一个节点的过程:

     * Here's the sequence of events for a deletion of node n with
     * predecessor b and successor f, initially:
     *
     *        +------+       +------+      +------+
     *   ...  |   b  |------>|   n  |----->|   f  | ...
     *        +------+       +------+      +------+
     *
     * 1. CAS n's value field from non-null to null.
     *    From this point on, no public operations encountering
     *    the node consider this mapping to exist. However, other
     *    ongoing insertions and deletions might still modify
     *    n's next pointer.
     *
     * 2. CAS n's next pointer to point to a new marker node.
     *    From this point on, no other nodes can be appended to n.
     *    which avoids deletion errors in CAS-based linked lists.
     *
     *        +------+       +------+      +------+       +------+
     *   ...  |   b  |------>|   n  |----->|marker|------>|   f  | ...
     *        +------+       +------+      +------+       +------+
     *
     * 3. CAS b's next pointer over both n and its marker.
     *    From this point on, no new traversals will encounter n,
     *    and it can eventually be GCed.
     *        +------+                                    +------+
     *   ...  |   b  |----------------------------------->|   f  | ...
     *        +------+                                    +------+
这个应该都能看懂,就是在要删除的节点后面追加一个标记节点,然后删除的时候将当前节点和标记节点一起从list中断开链接,这其中的操作都是cas操作。

看下Index节点,index节点就是skiplist的层次节点,有down,有right,因为持有的节点值不同和处理方式不同,所以跟node不能抽象:

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;
    }
    /**
     * compareAndSet right field
     */
    final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
        return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
    }
    /**
     * 节点是否被删除,因为删除一个index的时候会把index持有的node节点的value cas设置成null
     */
    final boolean indexesDeletedNode() {
        return node.value == null;
    }
    /**
     * link一个新的后继index,判断了当前index是否被删除,如果是被删除的index,再link就没有意义了
     */
    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);
    }
    /**
     * unlink后继index,如果当前是要删除的,就失败
     */
    final boolean unlink(Index<K,V> succ) {
        return !indexesDeletedNode() && 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继承Index,比index多了个level,skiplist每层的head:

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

看下最常用的2个构造:

//headIndex的node节点的value
private static final Object BASE_HEADER = new Object();
/**
 * skipList跳表的最上层第一个入口
 */
private transient volatile HeadIndex<K,V> head;
/**
 * 比较器,构造时可以传入,否则为null,使用key默认
 */
private final Comparator<? super K> comparator;
/**
 * 随机数种子
 */
private transient int randomSeed;

//根据构造传入comparator,如果没有,就是用key的默认比较
public ConcurrentSkipListMap() {
    this.comparator = null;
    initialize();
}
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
    initialize();
}
//构造调用初始化
final void initialize() {
    keySet = null;
    entrySet = null;
    values = null;
    descendingMap = null;
    randomSeed = seedGenerator.nextInt() | 0x0100; // ensure nonzero
	//这个最重要,head代表整个skiplist入口,构造的时候,新建第一层,index的down和right都null
    head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
                              null, null, 1); 
}
//cas设置head,还是unsafe的底层
private boolean casHead(HeadIndex<K,V> cmp, HeadIndex<K,V> val) {
    return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
}

ConcurrentSkipListMap是不支持key、value为null的。接下来看几个常用的方法,跟一下流程。

put方法:

public V put(K key, V value) {
    if (value == null)
        throw new NullPointerException();
    return doPut(key, value, false); //这里的是false,容许重复,ConcurrentSkipListSet内部使用ConcurrentSkipListMap保存数据,更新的时候用的是true
}
private V doPut(K kkey, V value, boolean onlyIfAbsent) {
    Comparable<? super K> key = comparable(kkey);
    for (;;) {
        Node<K,V> b = findPredecessor(key); //找到base-level的key节点的前驱node节点,然后顺手删除一些节点
        Node<K,V> n = b.next; //还记得b、n、f的意思,b是前驱节点,n是当前节点,f是后继节点
        for (;;) {
            if (n != null) {
                Node<K,V> f = n.next;
                if (n != b.next)               // skiplistmap是无锁list,所以关键操作都会检查,这里说明存在race,那就break,重新开始
                    break;
                Object v = n.value;
                if (v == null) {               // value为null说明n被删除
                    n.helpDelete(b, f); //n被删除,那就helpdelete,标记-删除,然后重试
                    break;
                }
                if (v == n || b.value == null) // 标记删除,如果n的value跟n一样,那就是标记节点,说明b是被删除的,那就重试
                    break;
                int c = key.compareTo(n.key); //上面检查暂时没问题,那就比较key
                if (c > 0) { 如果要put的key比当前n的大,说明b、n、f需要往后移一个位置,让后重试
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) { //key相同
                    if (onlyIfAbsent || n.casValue(v, value)) // 如果入参为true,不容许替换,那就返回,false容许替换,那就替换value值
                        return (V)v;
                    else
                        break; // 替换失败就重试
                }
                // else c < 0; fall through
            }
	
            Node<K,V> z = new Node<K,V>(kkey, value, n); //这里新建z指向n
            if (!b.casNext(n, z))
                break;         // casnext操作b的next由n指向新插入的z,失败就重试
			//在base-level插入新的node后,skiplist跳表需要将新建的node的随机插入其他层,保证i层存在的话,i-1层肯定存在,都是跳表的东西
            int level = randomLevel(); //随机生成的level
            if (level > 0) //如果level大于0那就插入
                insertIndex(z, level);
            return null;
        }
    }
}
//就是如果你构造提供了比较,那么排序的时候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;
}
/**
 * Compares using comparator or natural ordering. Used when the
 * ComparableUsingComparator approach doesn't apply.
 */
int compare(K k1, K k2) throws ClassCastException {
    Comparator<? super K> cmp = comparator;
    if (cmp != null)
        return cmp.compare(k1, k2);
    else
        return ((Comparable<? super K>)k1).compareTo(k2);
}
//找到base-level的key节点的前驱node节点,然后顺手删除一些节点
//基本上就是从head开始向右找,找到比给定的大的,就从大的index的前驱往下找
private Node<K,V> findPredecessor(Comparable<? super K> key) {
    if (key == null)
        throw new NullPointerException(); // don't postpone errors
    for (;;) {
        Index<K,V> q = head; //从head开始
        Index<K,V> r = q.right; //head的right
        for (;;) {
            if (r != null) { //r不为null就向右找
                Node<K,V> n = r.node;
                K k = n.key;
                if (n.value == null) { //value为null,则表示节点已经删除
                    if (!q.unlink(r)) //节点已经删除,那就从链表中unlink掉
                        break;           // restart 失败就重试
                    r = q.right;         // reread 节点删除后,重置r节点
                    continue;
                }
                if (key.compareTo(k) > 0) { //key大于当前节点的key,就一直往右移动
                    q = r;
                    r = r.right;
                    continue;
                }
            }
            Index<K,V> d = q.down; //上面找到了一个q节点,这个q的right的key比要找的key大,所以往down开始找
            if (d != null) {
                q = d; //这里就相当于往下移动了一层,然后继续for循环找
                r = d.right;
            } else
                return q.node; //如果d为null,说明找到了base-level那一层了,那就返回吧
        }
    }
}
//随机生成的level,0-31
private int randomLevel() {
    int x = randomSeed;
    x ^= x << 13;
    x ^= x >>> 17;
    randomSeed = x ^= x << 5;
    if ((x & 0x80000001) != 0) // &一半的机会返回0
        return 0;
    int level = 1;
    while (((x >>>= 1) & 1) != 0) ++level; //向右移位,在上面一半机会不为0的情况下,&再来一半机会为1,层数越高,机会越小,这样可以保证没那么多数据量还生成n多层
    return level;
}
//在随机生成的level插入index节点
private void insertIndex(Node<K,V> z, int level) {
    HeadIndex<K,V> h = head;
    int max = h.level;

    if (level <= max) { //如果随机生成的level比head的小
        Index<K,V> idx = null;
		//生成一个idx链表,每个down指向下一层的,假如放在3层,那就生成3个index,3层的down指向2层的,2层指向1层,1层的指向null,所有的index的node指向新插入的node
        for (int i = 1; i <= level; ++i) 
            idx = new Index<K,V>(z, idx, null); 
        //将index链表插入skiplist
		addIndex(idx, h, level);

    } else { // Add a new level
        level = max + 1; //如果随机的level大于已经有的跳表层级,那就增加一层
        Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1];
        Index<K,V> idx = null;
        for (int i = 1; i <= level; ++i)
            idxs[i] = idx = new Index<K,V>(z, idx, null); //一个index数组

        HeadIndex<K,V> oldh;
        int k;
        for (;;) {
            oldh = head; //原有的head和层级
            int oldLevel = oldh.level;
            if (level <= oldLevel) { // 这里可能有其他线程已经添加了一层了,所以不需要再新增一层
                k = level;
                break;
            }
			//下面是将新创建一个headIndex,并将down指向原来的headindex,right指向数组对应j的index,层级j
            HeadIndex<K,V> newh = oldh;
            Node<K,V> oldbase = oldh.node;
            for (int j = oldLevel+1; j <= level; ++j)
                newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
            if (casHead(oldh, newh)) { //cas替换跳表的head
                k = oldLevel; 
                break;
            }
        }
        addIndex(idxs[k], oldh, k); //在for循环里面是新增层级的添加index,这里就是其他层级的index添加
    }
}
/**
 * 将indexs插入skiplist
 */
private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) {
    // Track next level to insert in case of retries
    int insertionLevel = indexLevel;
    Comparable<? super K> key = comparable(idx.node.key);
    if (key == null) throw new NullPointerException();
    // 过程类似findPredecessor
    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) { //往右寻找key的位置
                Node<K,V> n = r.node;
                // compare before deletion check avoids needing recheck
                int c = key.compareTo(n.key);
                if (n.value == null) { //n删除,前驱节点断开当前节点n
                    if (!q.unlink(r))
                        break;
                    r = q.right;
                    continue;
                }
                if (c > 0) {
                    q = r;
                    r = r.right;
                    continue;
                }
            }

            if (j == insertionLevel) { //在某层找到了位置,就看下跳表的层级j和待插入的层次是否一样
                // 待插入index是删除的,return
                if (t.indexesDeletedNode()) {
                    findNode(key); // findNode方法会返回key的node,会顺手删除一些节点
                    return;
                }
                if (!q.link(r, t)) //待插入index没被删除,那就把前驱节点的right换成待插入的,失败就重试
                    break; // restart
                if (--insertionLevel == 0) { //待插入的为0了,那就是不需要再插入了
                    // need final deletion check before return
                    if (t.indexesDeletedNode()) //这里最后再检查下待插入index是否删除了
                        findNode(key);
                    return;
                }
            }

            if (--j >= insertionLevel && j < indexLevel)
                t = t.down; //这里就是待插入的index指向down,下次再插入的index就是待插入index下一层了
            q = q.down; //基本就是一层结束,然后down到下一层
            r = q.right;
        }
    }
}
//返回key的node,过程类似findPredecessor
private Node<K,V> findNode(Comparable<? super K> key) {
    for (;;) {
        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)                // 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)
                return n;
            if (c < 0)
                return null;
            b = n;
            n = f;
        }
    }
}

put方法基本就是现在base-level上找到前驱节点,然后插入节点,然后随机生成要插入的层级,如果比当前的小,就直接创建从上到下的index链,分别插入对应层级,如果大于当前,那么当前的就层级加1,然后生成数组,每个元素对应对应层级的链表,新创建一个headIndex,down指向原来的head,right指向数组对应的层级,最后用数组的对应的层级加入跳表对应的层级。代码看不懂,就多看,多想,真没其他好办法。

看下get()方法:

public V get(Object key) {
    return doGet(key);
}
private V doGet(Object okey) {
    Comparable<? super K> key = comparable(okey);
    /*
     * 容许重试
     */
    for (;;) {
        Node<K,V> n = findNode(key); 
        if (n == null)
            return null;
        Object v = n.value;
        if (v != null)
            return (V)v;
    }
}
我看的这个jdk版本doGet方法有的不一样,直接使用findNode获得node,没有像参考里面那哥们看的那版把所有find的过程都写在doGet的for()里面。

remove()方法:

public V remove(Object key) {
    return doRemove(key, null);
}
final V doRemove(Object okey, Object value) {
    Comparable<? super K> key = comparable(okey);
    for (;;) {
        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)                    // 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)
                return null;
            if (c > 0) {
                b = n;
                n = f;
                continue;
            }
            if (value != null && !value.equals(v))
                return null;
            if (!n.casValue(v, null)) //设置value为null,之前一直看到检测value==null来判断节点是否被删除
                break;
            if (!n.appendMarker(f) || !b.casNext(n, f))
                findNode(key);                  // Retry via findNode
            else {
                findPredecessor(key);           // Clean index
                if (head.right == null)
                    tryReduceLevel(); //right==null时有可能需要减少层级
            }
            return (V)v;
        }
    }
}
//减少层级,就是如果最高的连着3层都没有right都没有链接index,那就尝试cashead减少一层,然后recheck之前的head的right是否null,
//有可能有race导致这会又不会null,那就再casHead设置回来
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
}


最后看下size(),javadoc直接因为是无锁的list,说结果不一定准,所以这个方法不一定有用,太任性了:

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;
}
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; //直接找到第一个value不为null的就return
        n.helpDelete(b, n.next);
    }
}
看代码也能看出来,找到第一个value不为null的,然后从这个开始累加,因为free-lock,并发线程多的话,节点变动频繁,悲剧估计会经常发生。

ConcurrentSkipListMap的其他方法不想看了,都差不多。这个类看起来真累,主要是因为是跳表结构的,不太熟悉,而且又用的是lock-free的,所以在做各种操作的时候,总是检查链表的结构啊,节点是否被删除啊这些,没事还帮着清理下删除的node。


ConcurrentSkipListSet初始化的时候持有ConcurrentSkipListMap变量,然后add的时候:

public boolean add(E e) {
	return m.putIfAbsent(e, Boolean.TRUE) == null;
}
最后到ConcurrentSkipListMap:

public V putIfAbsent(K key, V value) {
    if (value == null)
        throw new NullPointerException();
    return doPut(key, value, true);
}

还记得skiplistmap的put是doput(key,value,false),最后一个putIfAbsent决定如果key对应的node存在是否还要替换,true不替换,false替换。skiplistset直接返回值。skiplistSet中其他的方法都类似的。


参考:

http://kenby.iteye.com/blog/1187303 跳表的原理

http://brokendreams.iteye.com/blog/2253955 源码分析,很nb的一个juc系列文章,基本上跟着看的

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:62405次
    • 积分:1347
    • 等级:
    • 排名:千里之外
    • 原创:62篇
    • 转载:72篇
    • 译文:1篇
    • 评论:15条
    最新评论