ConcurrentSkipListMap源码分析

看一下跳跃表的示意图,途中蓝色的为头节点,头节点指向的是普通索引节点

通过上图可以看到跳跃表的基本结构,下面分析一下普通索引节点和头节点的源码,可以发现头节点和普通索引节点的区别就是头节点有level的概念,而普通索引节点没有

static class Index<K,V> {
         //索引持有的数据节点
        final Node<K,V> node;
        //下一层索引
        final Index<K,V> down;
        //右侧索引
        volatile 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;
        }

        //CAS更新右侧索引
        final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
            return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
        }

        //判断当前节点是否是删除节点
        final boolean indexesDeletedNode() {
            return node.value == null;
        }

        //将newSucc节点插入到当前节点与succ节点之间
        final boolean link(Index<K,V> succ, Index<K,V> newSucc) {
            Node<K,V> n = node;
            //新索引节点的右侧索引指向succ
            newSucc.right = succ;
            //如果当前节点不是删除节点,则cas更新右侧索引指向newSucc
            return n.value != null && casRight(succ, newSucc);
        }

        //删除succ
        final boolean unlink(Index<K,V> succ) {
            return node.value != null && casRight(succ, succ.right);
        }

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

    //头节点,只有头节点才有层级概念
    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;
        }
    }

  

 public V put(K key, V value) {
         //如果value为null则抛出空指针异常
        if (value == null)
            throw new NullPointerException();
        //添加数据
        return doPut(key, value, false);
    }

    private V doPut(K key, V value, boolean onlyIfAbsent) {
        //声明一个需要添加到节点
        Node<K,V> z;
        //如果key为null则抛出一个NPE
        if (key == null)
            throw new NullPointerException();

        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
              //对当前key找到前驱
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                 //如果b的后继不为null
                if (n != null) {
                    Object v; int c;
                    //n的后继节点
                    Node<K,V> f = n.next;
                    //再次判断n是不是b的后继,如果不是则跳出循环(一致性读)
                    if (n != b.next)
                        break;
                     //如果n节点已经被删除则跳出循环
                    if ((v = n.value) == null) {
                        n.helpDelete(b, f);
                        break;
                    }
                    //如果b节点被删除则跳出循环
                    if (b.value == null || v == n)
                        break;
                     //如果key大于n节点的key则继续向后找
                    if ((c = cpr(cmp, key, n.key)) > 0) {
                        b = n;
                        n = f;
                        continue;
                    }
                    //如果找到了key相等的节点
                    if (c == 0) {
                        if (onlyIfAbsent || n.casValue(v, value)) {
                            @SuppressWarnings("unchecked") V vv = (V)v;
                            return vv;
                        }
                        break; //CAS竞争失败则继续
                    }
                    // else c < 0; fall through
                }
                //如果后继节点为null则可以直接添加
                z = new Node<K,V>(key, value, n);
                //CAS设置失败则跳出内层循环,继续执行
                if (!b.casNext(n, z))
                    break;
                //CAS成功则跳出外层循环
                break outer;
            }
        }

        int rnd = ThreadLocalRandom.nextSecondarySeed();
        //与10000000000000000000000000000001按位与的结果为0,表示最高位和最低位不为1
        if ((rnd & 0x80000001) == 0) { 

            int level = 1, max;
            //随机数从左到右连续有几个1,则level自增几次
            while (((rnd >>>= 1) & 1) != 0)
                ++level;
            Index<K,V> idx = null;
            HeadIndex<K,V> h = head;
            //如果level小于等于head节点的层级,则逐层创建索引且down之前idx
            if (level <= (max = h.level)) {
                for (int i = 1; i <= level; ++i)
                    idx = new Index<K,V>(z, idx, null);
            }
            else { //如果level大于head的层级
                //将level设置为max+1,即head的层级+1
                level = max + 1;
               //创建新的索引数组
               Index<K,V>[] idxs =
                    (Index<K,V>[])new Index<?,?>[level+1];
                //对索引数组进行赋值,0不使用,并将down指向前一个节点
                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) //如果当前level小于oldLevel则跳出
                        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);
                     //cas赋值头节点
                    if (casHead(h, newh)) {
                       //将最新头节点赋值给h
                        h = newh;
                        //idx为之前的头节点
                        idx = idxs[level = oldLevel];
                        break;
                    }
                }
            }
            // 找到插入点,并插入数据
            splice: for (int insertionLevel = level;;) {
              //level为之前头节点的层级,j为新头节点的层级
                int j = h.level;
                for (Index<K,V> q = h, r = q.right, t = idx;;) {
                        //如果新头节点,或者老头节点为null则跳出外层循环
                    if (q == null || t == null)
                        break splice;
                      //r为新头节点的右节点,如果不为null
                    if (r != null) {
                        //r索引持有的节点
                        Node<K,V> n = r.node;
                        //当前key和r索引持有的key进行比较
                        int c = cpr(cmp, key, n.key);
                        //如果n的值为null,则删除该节点,如果删除失败则重新循环
                        if (n.value == null) {
                            if (!q.unlink(r))
                                break;
                            r = q.right;
                            continue;
                        }
                        //如果key>n.key 则继续向后查找
                        if (c > 0) {
                            q = r;
                            r = r.right;
                            continue;
                        }
                    }
                    // 表示r为null,即找到该层的最后

                    //如果是需要插入的层级
                    if (j == insertionLevel) {
                        //将t插入到q,r之间失败则重新开始
                        if (!q.link(r, t))
                            break;
                         //插入成功,如果t的值为null,则需要删除
                        if (t.node.value == null) {
                            findNode(key);
                            break splice;
                        }
                        //如果到最低层则跳出外层循环
                        if (--insertionLevel == 0)
                            break splice;
                    }
                    //如果 --j 大于等于insertLevel则继续处理下一层
                    if (--j >= insertionLevel && j < level)
                        t = t.down;
                    q = q.down;
                    r = q.right;
                }
            }
        }
        return null;
    }

  

    private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
        //如果key为null则抛出NPE
        if (key == null)
            throw new NullPointerException(); // don‘t postpone errors
        for (;;) {
            // q初始化为头索引节点,r为索引节点右侧索引节点
            for (Index<K,V> q = head, r = q.right, d;;) {
                //如果r不为nul
                if (r != null) {
                    //r索引节点持有的数据节点
                    Node<K,V> n = r.node;
                    //该数据节点的key
                    K k = n.key;
                    //如果n的值为nul则表示该索引节点需要删除
                    if (n.value == null) {
                        //删除索引节点,删除失败则重新开始
                        if (!q.unlink(r))
                            break;
                         //删除成功后r为q的新右侧索引节点
                        r = q.right;
                        continue;
                    }
                    //r节点不是删除节点则比较key大小,r索引持有的key小于新插入的key则继续向右移
                    if (cpr(cmp, key, k) > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                //表示r为null或者r节点持有的数据key大于新插入数据的key

                //如果q下一层为null则直接返回q持有的数据节点
                if ((d = q.down) == null)
                    return q.node;
                //如果q.down不为null,则向下一层查找
                q = d;
                r = d.right;
            }
        }
    }

  

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

  private V doGet(Object key) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
            //b根据key找到的前驱节点
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                Object v; int c;
                //如果b的后继为null则跳出外层循环返回null
                if (n == null)
                    break outer;
                //f为n的后继节点
                Node<K,V> f = n.next;
                //如果n不是b的后继则跳出循环重新开始
                if (n != b.next)
                    break;
                 //如果n节点是需要删除节点则删除重新开始
                if ((v = n.value) == null) {
                    n.helpDelete(b, f);
                    break;
                }
                //如果b节点是删除节点重新开始
                if (b.value == null || v == n)
                    break;
                 //和n节点的key对比,如果相等则返回值
                if ((c = cpr(cmp, key, n.key)) == 0) {
                    @SuppressWarnings("unchecked") V vv = (V)v;
                    return vv;
                }
                //如果小于n节点的key则返回null( 因为key>p.key)
                if (c < 0)
                    break outer;
                //否则继续向后查询
                b = n;
                n = f;
            }
        }
        return null;
    }

  

原文地址:https://www.cnblogs.com/wei-zw/p/8810135.html

时间: 2024-11-09 05:00:54

ConcurrentSkipListMap源码分析的相关文章

死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug

前情提要 点击链接查看"跳表"详细介绍. 拜托,面试别再问我跳表了! 简介 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表. 跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找. 跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能. 存储结构 跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找. 源码分析 主要内部类 内部类跟存储结构结合着来看,大概能预测到代码的组织方式. // 数据节点,典型的单链表结构 static final

【JUC】JDK1.8源码分析之ConcurrentSkipListMap(二)

一.前言 最近在做项目的同时也在修复之前项目的一些Bug,所以忙得没有时间看源代码,今天都完成得差不多了,所以又开始源码分析之路,也着笔记录下ConcurrentSkipListMap的源码的分析过程. 二.ConcurrentSkipListMap数据结构 抓住了数据结构,对于理解整个ConcurrentSkipListMap有很重要的作用,其实,通过源码可知其数据结构如下. 说明:可以看到ConcurrentSkipListMap的数据结构使用的是跳表,每一个HeadIndex.Index结

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

NBA这赛季结束,勇士可惜啊,谁能想到没拿到冠军,库昊也没成为真正的老大,lbl一战封神,所有口水留言都变成羡慕嫉妒恨,哎,我库啊,还是还是看书吧. ConcurrentSkipListMap说实话,之前还真没注意过,还是看JUC才看到,利用skiplist跳表结构来实现一种有序的map,之前看到的map都是无序.在学习前还是要好好了解下什么是skiplist跳表,的确很不错,利用空间换时间,复杂度为logN,跳表的原理参考http://kenby.iteye.com/blog/1187303,

死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)CopyOnWriteArraySet以何种方式保证元素不重复? (5)如何比较两个Set中的元素是否完全一致? 简介 CopyOnWriteArraySet底层是使用CopyOnWriteArrayList存储元素的,所以它并不是使用Map来存储元素的. 但是,我们知道CopyOnWriteArra

死磕 java集合之ConcurrentSkipListSet源码分析——Set大汇总

问题 (1)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗? (2)ConcurrentSkipListSet是线程安全的吗? (3)ConcurrentSkipListSet是有序的吗? (4)ConcurrentSkipListSet和之前讲的Set有何不同? 简介 ConcurrentSkipListSet底层是通过ConcurrentNavigableMap来实现的,它是一个有序的线程安全的集合. 源码分析 它的源码比较简单,跟通过Map实

4. SOFAJRaft源码分析— RheaKV初始化做了什么?

前言 由于RheaKV要讲起来篇幅比较长,所以这里分成几个章节来讲,这一章讲一讲RheaKV初始化做了什么? 我们先来给个例子,我们从例子来讲: public static void main(final String[] args) throws Exception { final PlacementDriverOptions pdOpts = PlacementDriverOptionsConfigured.newConfigured() .withFake(true) // use a f

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu