跳表ConcurrentSkipListMap

很久没刷leetcode,今天刷leetcode时,遇到了跳表题目,传送门:中文版leetcode跳表题目,于是学习了下ConcurrentSkipListMap同时也是学习并发,关于ConcurrentSkipListMap是无锁的,跳表平时基本没用过,会讲解少许源码,但是不会细讲,因为关于这个源码讲解百度太多了,讲解之前,先把我抄袭的跳表代码贴出来,去除了并发操作,但是放入leetcode执行时,超时了...

import java.util.Comparator;
import java.util.concurrent.atomic.AtomicLong;

public class SkipList<K,V> {

    private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL;
    private static final AtomicLong seeder = new AtomicLong(initialSeed());
    private static  long SEED;
    private static  long SECONDARY;

    private static final Object BASE_HEADER = new Object();

    private HeadIndex<K,V> head;
    final Comparator<? super K> comparator ;

    public SkipList(){
        this(null);
    }
    public SkipList(Comparator<? super K> comparator) {
        this.comparator = comparator;
        initialize();
    }
    private void initialize() {
        head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
                null, null, 1);
    }

    public V get(Object key) {
        return doGet(key);
    }

    private V doGet(Object key) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
            int c;
            if (n == null)
                break;
            Node<K,V> f = n.next;
            Object v = n.value;
            if ((c = cpr(cmp, key, n.key)) == 0) {
                V vv = (V)v;
                return vv;
            }
            if (c < 0)
                break;
            b = n;
            n = f;
        }
        return null;
    }

    public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        return doPut(key, value);
    }
    private V doPut(K key, V value) {
        Node<K,V> z;             // added node
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
            if (n != null) {
                int c;
                Node<K,V> f = n.next;
                if ((c = cpr(cmp, key, n.key)) > 0) {
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) {
                    n.value = value;
                    V vv = (V)n.value;
                    return vv;
                }
            }
            z = new Node<K,V>(key, value, n);
            b.next = z;
            break ;
        }
        int rnd = nextSecondarySeed();
        if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
            int level = 1, max;
            while (((rnd >>>= 1) & 1) != 0)
                ++level;
            Index<K,V> idx = null;
            HeadIndex<K,V> h = head;
            if (level <= (max = h.level)) {
                for (int i = 1; i <= level; ++i)
                    idx = new Index<K,V>(z, idx, null);
            }
            else { // try to grow by one level
                level = max + 1; // hold in array and later pick the one to use
                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);
                for (;;) {
                    h = head;
                    int oldLevel = h.level;
                    if (level <= oldLevel) // lost race to add level
                        break;
                    HeadIndex<K,V> newh = h;
                    Node<K,V> oldbase = h.node;
                    for (int j = oldLevel+1; j <= level; ++j)
                        newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                    h = newh;
                    idx = idxs[level = oldLevel];
                    break;
                }
            }
            // find insertion points and splice in
            int insertionLevel = level;
            int j = h.level;
            for (Index<K,V> q = h, r = q.right, t = idx;;) {
                if (q == null || t == null)
                    break ;
                if (r != null) {
                    Node<K,V> n = r.node;
                    // compare before deletion check avoids needing recheck
                    int c = cpr(cmp, key, n.key);
                    if (c > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if (j == insertionLevel) {
                    q.right = t;
                    t.right = r;
                    if (--insertionLevel == 0)
                        break ;
                }
                if (--j >= insertionLevel && j < level)
                    t = t.down;
                q = q.down;
                r = q.right;
            }
        }
        return null;
    }
    public V remove(Object key) {
        return doRemove(key, null);
    }

    final V doRemove(Object key, Object value) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
            int c;
            if (n == null)
                break ;
            Node<K,V> f = n.next;
            Object v = n.value;
            if ((c = cpr(cmp, key, n.key)) < 0)
                break ;
            if (c > 0) {
                b = n;
                n = f;
                continue;
            }
            if (value != null && !value.equals(v))
                break ;
            b.next = f;
            n.value = null;
            findPredecessor(key, cmp);      // clean index
            if (head.right == null)
                tryReduceLevel();
            V vv = (V)v;
            return vv;
        }
        return null;
    }
    private void tryReduceLevel() {
        HeadIndex<K,V> h = head;
        HeadIndex<K,V> d;
        HeadIndex<K,V> e;
        if (h.level > 3 &&
                (d = (HeadIndex<K,V>)h.down) != null &&
                (e = (HeadIndex<K,V>)d.down) != null &&
                e.right == null &&
                d.right == null &&
                h.right == null )
            h = d;
    }
    private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
        if (key == null)
            throw new NullPointerException(); // don't postpone errors
        for (Index<K,V> q = head, r = q.right, d;;) {
            if (r != null) {
                Node<K,V> n = r.node;
                K k = n.key;
                if (n.value == null) {
                    q.right = r.right;
                    continue;
                }
                if (cpr(cmp, key, k) > 0) {
                    q = r;
                    r = r.right;
                    continue;
                }
            }
            if ((d = q.down) == null)
                return q.node;
            q = d;
            r = d.right;
        }
    }
    static final int cpr(Comparator c, Object x, Object y) {
        return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y);
    }
    private static long initialSeed() {
        String pp = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                        "java.util.secureRandomSeed"));
        if (pp != null && pp.equalsIgnoreCase("true")) {
            byte[] seedBytes = java.security.SecureRandom.getSeed(8);
            long s = (long)(seedBytes[0]) & 0xffL;
            for (int i = 1; i < 8; ++i)
                s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
            return s;
        }
        return (mix64(System.currentTimeMillis()) ^
                mix64(System.nanoTime()));
    }
    private static final int nextSecondarySeed() {
        int r;
        Thread t = Thread.currentThread();
        if ((r = (int)SECONDARY) != 0) {
            r ^= r << 13;   // xorshift
            r ^= r >>> 17;
            r ^= r << 5;
        }
        else {
            localInit();
            if ((r = (int)SEED) == 0)
                r = 1; // avoid zero
        }
        SECONDARY = r;
        return r;
    }
    private static final void localInit() {
        SEED = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    }
    private static long mix64(long z) {
        z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
        z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
        return z ^ (z >>> 33);
    }
    static final class Node<K,V> {
        final K key;
        Object value;
        Node<K,V> next;

        Node(K key, Object value, Node<K,V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }
    static class Index<K,V> {
        final Node<K,V> node;
        final Index<K,V> down;
        Index<K,V> right;

        Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
            this.node = node;
            this.down = down;
            this.right = 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;
        }
    }
}

但leetcode的key值是int没有value值,所以整理leetcode的代码如下:

import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

public class Skiplist {

    private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL;
    private static final AtomicLong seeder = new AtomicLong(initialSeed());
    private static  long SEED;
    private static  long SECONDARY;

    private HeadIndex head;
    final Comparator comparator = null;
    private Random random = new Random();

    public Skiplist() {
        head = new HeadIndex(new Node(null, null),
                null, null, 1);
    }
    public boolean search(int target) {
        return doGet(target);
    }
    private boolean doGet(int key) {
        Comparator cmp = comparator;
        for (Node b = findPredecessor(key, cmp), n = b.next;;) {
            int c;
            if (n == null)
                break;
            Node f = n.next;
            if ((c = cpr(cmp, key, n.key)) == 0) {
                return true;
            }
            if (c < 0)
                break;
            b = n;
            n = f;
        }
        return false;
    }
    public void add(int num) {
        doPut(num);
    }
    private void doPut(int key) {
        Node z;             // added node
        Comparator cmp = comparator;
        for (Node b = findPredecessor(key, cmp), n = b.next;;) {
            if (n != null) {
                int c;
                Node f = n.next;
                if ((c = cpr(cmp, key, n.key)) > 0) {
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) {
                    return ;
                }
            }
            z = new Node(key, n);
            b.next = z;
            break ;
        }
        int rnd = nextSecondarySeed();
       // int rnd = random.nextInt();
        if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
            int level = 1, max;
            while (((rnd >>>= 1) & 1) != 0)
                ++level;
            Index idx = null;
            HeadIndex h = head;
            if (level <= (max = h.level)) {
                for (int i = 1; i <= level; ++i)
                    idx = new Index(z, idx, null);
            }
            else { // try to grow by one level
                level = max + 1; // hold in array and later pick the one to use
                Index[] idxs = (Index[])new Index[level+1];
                for (int i = 1; i <= level; ++i)
                    idxs[i] = idx = new Index(z, idx, null);
                h = head;
                int oldLevel = h.level;
                if (level > oldLevel) {
                    HeadIndex newh = h;
                    Node oldbase = h.node;
                    for (int j = oldLevel+1; j <= level; ++j)
                        newh = new HeadIndex(oldbase, newh, idxs[j], j);
                    h = newh;
                    idx = idxs[level = oldLevel];
                }

            }
            // find insertion points and splice in
            int insertionLevel = level;
            int j = h.level;
            for (Index q = h, r = q.right, t = idx;;) {
                if (q == null || t == null)
                    break ;
                if (r != null) {
                    Node n = r.node;
                    // compare before deletion check avoids needing recheck
                    int c = cpr(cmp, key, n.key);
                    if (c > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if (j == insertionLevel) {
                    q.right = t;
                    t.right = r;
                    if (--insertionLevel == 0)
                        break ;
                }
                if (--j >= insertionLevel && j < level)
                    t = t.down;
                q = q.down;
                r = q.right;
            }
        }
    }
    public boolean erase(int num) {
        return doRemove(num);
    }
    final boolean doRemove(int key) {
        Comparator cmp = comparator;
        for (Node b = findPredecessor(key, cmp), n = b.next;;) {
            int c;
            if (n == null)
                break ;
            Node f = n.next;
            if ((c = cpr(cmp, key, n.key)) < 0)
                break ;
            if (c > 0) {
                b = n;
                n = f;
                continue;
            }
            b.next = f;
            n.isDel = true;
           // findPredecessor(key, cmp);      // clean index
            if (head.right == null)
                tryReduceLevel();
            return true;
        }
        return false;
    }
    private void tryReduceLevel() {
        HeadIndex h = head;
        HeadIndex d;
        HeadIndex e;
        if (h.level > 3 &&
                (d = (HeadIndex)h.down) != null &&
                (e = (HeadIndex)d.down) != null &&
                e.right == null &&
                d.right == null &&
                h.right == null )
            h = d;
    }
    private Node findPredecessor(int key, Comparator cmp) {
        for (Index q = head, r = q.right, d;;) {
            if (r != null) {
                Node n = r.node;
                Integer k = n.key;
                if (n.isDel) {
                    q.right = r.right;
                    continue;
                }
                if (cpr(cmp, key, k) > 0) {
                    q = r;
                    r = r.right;
                    continue;
                }
            }
            if ((d = q.down) == null)
                return q.node;
            q = d;
            r = d.right;
        }
    }
    static final class Node {
        final Integer key;
        Node next;
        boolean isDel;
        
        Node(Integer key, Node next) {
            this.key = key;
            this.next = next;
        }
    }
    static class Index {
        final Node node;
        final Index down;
        Index right;

        Index(Node node, Index down, Index right) {
            this.node = node;
            this.down = down;
            this.right = right;
        }
    }
    static final class HeadIndex extends Index {
        final int level;
        HeadIndex(Node node, Index down, Index right, int level) {
            super(node, down, right);
            this.level = level;
        }
    }
    static final int cpr(Comparator c, Object x, Object y) {
        return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y);
    }
    private static long initialSeed() {
//        String pp = java.security.AccessController.doPrivileged(
//                new sun.security.action.GetPropertyAction(
//                        "java.util.secureRandomSeed"));
//        if (pp != null && pp.equalsIgnoreCase("true")) {
//            byte[] seedBytes = java.security.SecureRandom.getSeed(8);
//            long s = (long)(seedBytes[0]) & 0xffL;
//            for (int i = 1; i < 8; ++i)
//                s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
//            return s;
//        }
        return (mix64(System.currentTimeMillis()) ^
                mix64(System.nanoTime()));
    }
    private static final int nextSecondarySeed() {
        int r;
        Thread t = Thread.currentThread();
        if ((r = (int)SECONDARY) != 0) {
            r ^= r << 13;   // xorshift
            r ^= r >>> 17;
            r ^= r << 5;
        }
        else {
            localInit();
            if ((r = (int)SEED) == 0)
                r = 1; // avoid zero
        }
        SECONDARY = r;
        return r;
    }
    private static final void localInit() {
        SEED = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    }
    private static long mix64(long z) {
        z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
        z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
        return z ^ (z >>> 33);
    }
}

这个代码最终超时了,并非报错,超时的测试用例为:
["Skiplist","add","add","add","add","add","add","add","add","add","erase","search","add","erase","erase","erase","add","search","search","search","erase","search","add","add","add","erase","search","add","search","erase","search","search","erase","erase","add","erase","search","erase","erase","search","add","add","erase","erase","erase","add","erase","add","erase","erase","add","add","add","search","search","add","erase","search","add","add","search","add","search","erase","erase","search","search","erase","search","add","erase","search","erase","search","erase","erase","search","search","add","add","add","add","search","search","search","search","search","search","search","search","search"]
[[],[16],[5],[14],[13],[0],[3],[12],[9],[12],[3],[6],[7],[0],[1],[10],[5],[12],[7],[16],[7],[0],[9],[16],[3],[2],[17],[2],[17],[0],[9],[14],[1],[6],[1],[16],[9],[10],[9],[2],[3],[16],[15],[12],[7],[4],[3],[2],[1],[14],[13],[12],[3],[6],[17],[2],[3],[14],[11],[0],[13],[2],[1],[10],[17],[0],[5],[8],[9],[8],[11],[10],[11],[10],[9],[8],[15],[14],[1],[6],[17],[16],[13],[4],[5],[4],[17],[16],[7],[14],[1]]
然后我去看了题解,发现找出了两个答案一个是数组实现,一个双链表实现,数组的实现占用了大量的空间。Doug Lea也说过为什么不用数组实现,1)基于数组的实现似乎遇到了更多的复杂性和开销2)我们可以为遍历索引列表使用比基础列表更便宜的算法。ConcurrentSkipListMap注释上有写到在123行。双链表的实现和ConcurrentSkipListMap的实现思想大致相同,双链表代码的随机算法我不太认同因为每个层级的数据多少都是随机。我也不太想去找我的代码超时的原因了,第一:leetcode运行机制我不清楚,他们的超时是怎么计算的,第二:我不知道leetcode使用的那个jdk以及jdk版本,sun.security.action这个代码编译报错了。
上文又提到sun.security.action代码编译报错,也就是要讲解的随机算法,这是因为ConcurrentSkipListMap随机算法的实现用到了ThreadLocalRandom.nextSecondarySeed(),但nextSecondarySeed又不是public的,所以我把nextSecondarySeed实现提取了出来。ThreadLocalRandom.nextSecondarySeed和Random.next()随机算法实现不一样并且作者和实现的jdk版本也不一样,所以这两个算法我都进行了大量测试,发现最终实现的层级基本一致以及每个层级的数据多少都差不多,也就是说这两个方法的效果差不多。我在解释下随机算法中的几行代码:

if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
            int level = 1, max;
            while (((rnd >>>= 1) & 1) != 0)
                ++level;
  ...
}

第一行0x80000001二进制表示为1000 0000 0000 0000 0000 0000 0000 0001 然后与1做按位与,如果等于 0,说明这个随机数最高位和最低位都为 0,第三行((rnd >>>= 1) & 1) != 0,移除最后一位,低位连续为 1 的个数作为 level 的值。网上还有几种随机算法Random.next..然后与某个数比较,这种方法我没测试,毕竟Doug Lea实现了一种随机算法。
记录下ConcurrentSkipListMap并发问题,毕竟这个是重点,看源码时类的开头一般都有全文简介,这个一般是设计思想,一定要看注释,一定要看注释,一定要看注释。Unsafe类是并发包的基础。一般的用法为:

private volatile  X x;
for(;;){
   ...
   if(unsafe.compareAndSwapObject(...)){
      ... //得到控制权,做某些事 或者存储数据,然后退出循环
      unsafe.putOrderedObject(x,...);
      break;
    }
  ...//没得到控制权,判断是否有多线程并发问题,然后继续循环
}

一般的运用如上述的代码并配合volatile,在获取数据时,首先将volatile赋值给临时变量,利用volatile的可见性获取最新数据,而如何处理并发问题,就要看具体的思路了。比如ConcurrentSkipListMap为什么用链表实现?为什么删除不直接删除数据,而是将value的值设成null?为什么会有marker节点?等等,一定要看注释,答案都在注释上。不详解源码,给一个链接这个文章翻译了注释并且也有一部分源码注解,传送门:http://08643.cn/p/9268ae6cbe6f

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容