• 【JUC源码解析】ConcurrentSkipListMap


    简介

    基于跳表,支持并发,有序的哈希表。

    跳表

    红色路径为寻找结点F。

    拿空间换时间,时间复杂度,O(nlogn).

    源码分析

    内部类

    Node

    属性

    1         final K key; //
    2         volatile Object value; //
    3         volatile Node<K,V> next; // 指向下一个结点

    最底层是基础层,即使结点(Node)层,保存实际数据(value),next指向下一个结点

    构造方法

     1         Node(K key, Object value, Node<K,V> next) { // 构造方法
     2             this.key = key;
     3             this.value = value;
     4             this.next = next;
     5         }
     6 
     7         Node(Node<K,V> next) { // 构造方法,用来构建标记结点,特点是值为自身
     8             this.key = null;
     9             this.value = this;
    10             this.next = next;
    11         }

    标记(marker)结点的value值是自身。 

    核心方法

     1         boolean isBaseHeader() { // 是否为头结点(每级)
     2             return value == BASE_HEADER;
     3         }
     4 
     5         boolean appendMarker(Node<K,V> f) { // 插入标记结点
     6             return casNext(f, new Node<K,V>(f));
     7         }
     8 
     9         void helpDelete(Node<K,V> b, Node<K,V> f) { // 帮助删除
    10             if (f == next && this == b.next) { // 如果b和f分别是自己的前驱结点和后继结点
    11                 if (f == null || f.value != f) // 当前结点还没有被标记删除(后接标记结点)
    12                     casNext(f, new Node<K,V>(f)); // 直接删除当前结点
    13                 else // 如果已经标记为删除,则一次性删除当前结点后标记结点
    14                     b.casNext(this, f.next);
    15             }
    16         }

    Index

    属性

    1         final Node<K,V> node; // 指向实际结点
    2         final Index<K,V> down; // 指向下级索引
    3         volatile Index<K,V> right; // 指向右侧索引

     自第一层往上,是索引层,各层的node域均指向垂直向下的结点(Node),down指向下面一层的索引,right指向右边一个索引

    构造方法

    1         Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) { // 构造方法
    2             this.node = node;
    3             this.down = down;
    4             this.right = right;
    5         }

    核心方法

     1         final boolean indexesDeletedNode() { // 删除索引结点
     2             return node.value == null;
     3         }
     4 
     5         final boolean link(Index<K,V> succ, Index<K,V> newSucc) { // 链接(右侧插入)新索引
     6             Node<K,V> n = node; // 当前索引的node域
     7             newSucc.right = succ; // 新索引的right域名设为当前索引的右侧索引
     8             return n.value != null && casRight(succ, newSucc); // 若node结点没被删除,设置新索引到当前索引的right域
     9         }
    10 
    11         final boolean unlink(Index<K,V> succ) { // 解除当前索引的右侧索引
    12             return node.value != null && casRight(succ, succ.right); // 若node结点没被删除,设置右侧索引的右侧索引到当前索引的right域
    13         }

    HeadIndex

    1     static final class HeadIndex<K,V> extends Index<K,V> { // 继承Index
    2         final int level; // 级别
    3         HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
    4             super(node, down, right);
    5             this.level = level;
    6         }
    7     }

    索引层最左边的索引,记录级别(层数)

    数据结构

    横向看,最下面一层是基础层,即是结点层,从第一层开始往上是索引层 ,head指向顶层最左边的索引。

    纵向看,最左边一列是层数,0层(虚构的)是结点层,第二列第一行往上是头索引,右上是索引结点。

    每个索引结点都有right域,指向右边的索引,也有down域,指向下面的索引,第一层索引的down域指向null,所有索引的node域均指向其下方的Node结点

    基础层(结点层)有next域,指向右边的结点。

    头索引有level属性,记录层级。

    属性

     1     private static final Object BASE_HEADER = new Object(); // 标明基础层(Node层)头节点
     2 
     3     private transient volatile HeadIndex<K,V> head; // 最顶层头节点
     4 
     5     final Comparator<? super K> comparator; // 比较器
     6 
     7     private transient KeySet<K> keySet; // 键集合
     8     
     9     private transient EntrySet<K,V> entrySet; // 键值对集合
    10     
    11     private transient Values<V> values; // 值集合
    12     
    13     private transient ConcurrentNavigableMap<K,V> descendingMap; // 降序(键)集合

    构造方法

     1     public ConcurrentSkipListMap() {
     2         this.comparator = null;
     3         initialize();
     4     }
     5 
     6     public ConcurrentSkipListMap(Comparator<? super K> comparator) {
     7         this.comparator = comparator;
     8         initialize();
     9     }
    10 
    11     public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
    12         this.comparator = null;
    13         initialize();
    14         putAll(m); // 将m中的元素加入跳表
    15     }
    16 
    17     public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
    18         this.comparator = m.comparator();
    19         initialize();
    20         buildFromSorted(m); // 根据m的元素顺序批量构建跳表
    21     }

    核心方法

    initialize()

    1     private void initialize() {
    2         keySet = null;
    3         entrySet = null;
    4         values = null;
    5         descendingMap = null;
    6         head = new HeadIndex<K, V>(new Node<K, V>(null, BASE_HEADER, null), null, null, 1); // Node.value = BASE_HEADER, 基础层(Node层)头节点
    7     }

    doPut(K, V, boolean)

    方法签名

    1     private V doPut(K key, V value, boolean onlyIfAbsent) {
    2         
    3     }

    寻找插入点,构建新结点,完成基础层(Node层)的插入操作

     1         Node<K, V> z; // 指向待插入结点
     2         if (key == null) // 参数校验,键为空,抛出空指针异常
     3             throw new NullPointerException();
     4         Comparator<? super K> cmp = comparator; // 比较器
     5         outer: for (;;) { // 外层循环
     6             for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) { // n为当前结点,b是n的前驱结点,新结点是要插入到b和n之间的
     7                 if (n != null) {
     8                     Object v; // 指向当前结点的value
     9                     int c; // 比较key的结果
    10                     Node<K, V> f = n.next; // n的后继结点
    11                     if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
    12                         break; // 重新读取
    13                     if ((v = n.value) == null) { // 如果n被删除
    14                         n.helpDelete(b, f); // 去帮助删除,使其尽快结束
    15                         break; // 重新读取
    16                     }
    17                     if (b.value == null || v == n) // b结点被删除(其value为null,或其后继结点是marker结点)
    18                         break;
    19                     if ((c = cpr(cmp, key, n.key)) > 0) { // key大于n结点的key值,因为要插入到当前结点的前面,所以不满足要求,需要右移
    20                         b = n;
    21                         n = f; // 右移
    22                         continue; // 继续
    23                     }
    24                     if (c == 0) { // 键相等,替换
    25                         if (onlyIfAbsent || n.casValue(v, value)) {
    26                             @SuppressWarnings("unchecked")
    27                             V vv = (V) v;
    28                             return vv; // 返回旧值
    29                         }
    30                         break; // CAS失败,有别的线程捣乱,重来
    31                     }
    32                 }
    33 
    34                 z = new Node<K, V>(key, value, n); // 构建新结点
    35                 if (!b.casNext(n, z))
    36                     break; // CAS失败,有别的线程捣乱,重来
    37                 break outer; // 成功,则跳出外层循环
    38             }
    39         }

    随机选取层(大于当前最大层,新增一层,同时更新顶层头索引head),构建纵向索引链

     1         int rnd = ThreadLocalRandom.nextSecondarySeed(); // 获取一个线程无关的随机数,int类型,32位
     2         if ((rnd & 0x80000001) == 0) { // 最高位和最低位为1的情况下,除基础层新增结点外,各层均不再增索引
     3             int level = 1, max;
     4             while (((rnd >>>= 1) & 1) != 0) // 低位(从第2位开始)连续为1的个数,作为选取层
     5                 ++level;
     6             Index<K, V> idx = null; // 指向从顶层开始第一个需要调整的索引,一般是选择层level,如果level大于max,说明需要新增一层,而新增的那层(level层)不需要调整(head->HeadIndex)指向它就行,所以此时,idx指向level-1层新增的索引
     7             HeadIndex<K, V> h = head; // 顶层头索引
     8             if (level <= (max = h.level)) { // 如果选取的层没有超出最大层
     9                 for (int i = 1; i <= level; ++i) //构建一个从 1 层到 level层的纵纵向索引(Index)链
    10                     idx = new Index<K, V>(z, idx, null); // 此时,idx指向level层新增的索引
    11             } else { // 如果选取层超过了最大层,则增加一层索引
    12                 level = max + 1; // 此时,level为老的最大层加1
    13                 @SuppressWarnings("unchecked")
    14                 Index<K, V>[] idxs = (Index<K, V>[]) new Index<?, ?>[level + 1]; // 用长度为level+1的数组,保存各层(1到level层,数组[0]未使用,为的是索引的层数与其所在数组的下标相等)新增索引的引用
    15                 for (int i = 1; i <= level; ++i)
    16                     idxs[i] = idx = new Index<K, V>(z, idx, null); // 每层新增索引保存在对应其层数的下标位置处,idx最后指向level层新增的索引
    17                 for (;;) {
    18                     h = head; // 再次获取顶层头索引
    19                     int oldLevel = h.level; // 获取老的最大层
    20                     if (level <= oldLevel) // 如果选取层又比oldLevel小了,说明,别的线程抢先更新过跳表了
    21                         break; // 跳出循环,idx最后指向level层的索引,同没有超出最大层的情况
    22                     HeadIndex<K, V> newh = h;
    23                     Node<K, V> oldbase = h.node;
    24                     for (int j = oldLevel + 1; j <= level; ++j) // 更新新增层纵向头索引,正常情况下只新增一层
    25                         newh = new HeadIndex<K, V>(oldbase, newh, idxs[j], j); // newh最后指向顶层头索引
    26                     if (casHead(h, newh)) { // CAS head(head始终指向顶层头索引)
    27                         h = newh; // h也指向顶层头索引
    28                         idx = idxs[level = oldLevel]; // idx指向老的顶层头索引
    29                         break; // 跳出循环
    30                     }
    31                 }
    32             }

    自选取层至底层,连接各层新增索引,完成跳表的构建

     1             splice: for (int insertionLevel = level;;) {
     2                 int j = h.level;
     3                 for (Index<K, V> q = h, r = q.right, t = idx;;) {
     4                     if (q == null || t == null) // 头节点被删除,或者新增索引为空,直接跳出外层循环
     5                         break splice;
     6                     if (r != null) {
     7                         Node<K, V> n = r.node; // 获得右索引结点
     8                         int c = cpr(cmp, key, n.key); // 比较key
     9                         if (n.value == null) { // n(正在)被删除
    10                             if (!q.unlink(r)) // 解除r索引
    11                                 break; // 如果失败,说明有别的线程干预,跳出内循环,重新获取level
    12                             r = q.right; // 获取新的右索引
    13                             continue; // 继续
    14                         }
    15                         if (c > 0) { // key大于n结点的key值,需要右移
    16                             q = r;
    17                             r = r.right; // 右移
    18                             continue; // 继续
    19                         }
    20                     }
    21 
    22                     if (j == insertionLevel) {
    23                         if (!q.link(r, t)) // 将t插在q和r之间
    24                             break; // 如果失败,跳出内循环,重试
    25                         if (t.node.value == null) { // t索引指向的结点被删除
    26                             findNode(key); // 清理删除的结点
    27                             break splice; // 跳出外层循环
    28                         }
    29                         if (--insertionLevel == 0) // 处理结束
    30                             break splice; // 跳出外层循环
    31                     }
    32 
    33                     if (--j >= insertionLevel && j < level)
    34                         t = t.down; // 处理下一层索引
    35                     q = q.down; // 同步更新
    36                     r = q.right; // 同步更新
    37                 }
    38             }

    doRemove(Object, Object)

     1     final V doRemove(Object key, Object value) {
     2         if (key == null)
     3             throw new NullPointerException();
     4         Comparator<? super K> cmp = comparator;
     5         outer: for (;;) {
     6             for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) { // 寻找key的前驱结点,若key在跳表中,结点n即是key在跳表的结点
     7                 Object v;
     8                 int c;
     9                 if (n == null) // n被删除,直接跳出外层循环
    10                     break outer;
    11                 Node<K, V> f = n.next; // n的后继结点
    12                 if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
    13                     break; // 重新读取
    14                 if ((v = n.value) == null) { // n被删除
    15                     n.helpDelete(b, f); // 帮助删除
    16                     break; // 重新检查
    17                 }
    18                 if (b.value == null || v == n) // b被删除(其value为null,或其后继结点是marker结点)
    19                     break;
    20                 if ((c = cpr(cmp, key, n.key)) < 0) // key所对应的结点不存在,b < x(key) < n, b -> n
    21                     break outer; // 直接退出外层循环
    22                 if (c > 0) { // 要找的结点还在n后面,右移
    23                     b = n;
    24                     n = f;
    25                     continue;
    26                 }
    27                 if (value != null && !value.equals(v)) // 传入的value不等于结点的value值,说明别的线程已经修改过了,不予删除
    28                     break outer;
    29                 if (!n.casValue(v, null)) // CAS v -> null
    30                     break; // CAS失败,则重新来过
    31                 if (!n.appendMarker(f) || !b.casNext(n, f)) // 尝试在n结点后接marker结点,如果失败,则重试;若成功,则尝试CAS b结点的next域,失败,也重试
    32                     findNode(key); // 清理删除的结点
    33                 else {
    34                     findPredecessor(key, cmp); // 清理无用的索引
    35                     if (head.right == null)
    36                         tryReduceLevel(); // 并清除没有索引的层
    37                 }
    38                 @SuppressWarnings("unchecked")
    39                 V vv = (V) v;
    40                 return vv;
    41             }
    42         }
    43         return null;
    44     }

    查找到要删除的结点后,首先CAS其value为null,失败重试,若成功后,继续在其后添加marker结点,接着CAS其前驱结点(b)的next为其后继结点(f),若都成功,则结束,否则, 调用findNode方法,该方法会遍历跳表,并帮助删除value为null的结点。

    marker结点的作用是为了降低b结点的并发性,若是没有marker结点,那么所有的CAS压力全集中在了结点上,若是有marker结点,那么会首先对n结点(待删除结点)CAS,成功后,才去找b结点,否则,直接就调用findNode方法,以期清理待删除的结点。

    doGet(Object)

     1     private V doGet(Object key) {
     2         if (key == null)
     3             throw new NullPointerException();
     4         Comparator<? super K> cmp = comparator;
     5         outer: for (;;) {
     6             for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) {
     7                 Object v;
     8                 int c;
     9                 if (n == null) // n被删除,直接跳出
    10                     break outer;
    11                 Node<K, V> f = n.next;
    12                 if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
    13                     break; // 重新读取
    14                 if ((v = n.value) == null) { // n正在被删除
    15                     n.helpDelete(b, f); // 帮助删除
    16                     break;
    17                 }
    18                 if (b.value == null || v == n) // b已经被删除
    19                     break;
    20                 if ((c = cpr(cmp, key, n.key)) == 0) { // 找到结点
    21                     @SuppressWarnings("unchecked")
    22                     V vv = (V) v;
    23                     return vv;
    24                 }
    25                 if (c < 0) // key所对应的结点不存在,b < x(key) < n, b -> n
    26                     break outer;
    27                 b = n; // 右移,继续
    28                 n = f;
    29             }
    30         }
    31         return null;
    32     }

     for (;;) {

        for (;next;)

            if(!cas){

                 break;

            }

        }

    findFirst()

    1     final Node<K, V> findFirst() {
    2         for (Node<K, V> b, n;;) {
    3             if ((n = (b = head.node).next) == null) // 头结点不是数据结点,所以要从第二个开始,如果为空,返回null
    4                 return null;
    5             if (n.value != null) // 结点(第二个结点,第一个数据结点)没被删除,返回此结点
    6                 return n;
    7             n.helpDelete(b, n.next); // 否则,清理已经删除结点,继续往后找
    8         }
    9     }

    findPredecessor(Object, Comparator<? super K>)

     1     private Node<K, V> findPredecessor(Object key, Comparator<? super K> cmp) {
     2         if (key == null)
     3             throw new NullPointerException();
     4         for (;;) {
     5             for (Index<K, V> q = head, r = q.right, d;;) { // 自顶层头索引开始查找
     6                 if (r != null) {
     7                     Node<K, V> n = r.node;
     8                     K k = n.key;
     9                     if (n.value == null) { // 正在被删除
    10                         if (!q.unlink(r)) // 清除r索引
    11                             break; // 失败重来
    12                         r = q.right; // 重新读取r
    13                         continue; // 继续
    14                     }
    15                     if (cpr(cmp, key, k) > 0) { // key比结点的k大,需要往后查找
    16                         q = r;
    17                         r = r.right; // 右移
    18                         continue; // 继续
    19                     }
    20                 }
    21                 if ((d = q.down) == null) // 最底层索引,再往下是基础层(结点层)
    22                     return q.node;
    23                 q = d; // 继续右下查找
    24                 r = d.right;
    25             }
    26         }
    27     }

    查找前驱结点 

    findNode(Object)

     1     private Node<K, V> findNode(Object key) {
     2         if (key == null)
     3             throw new NullPointerException();
     4         Comparator<? super K> cmp = comparator;
     5         outer: for (;;) {
     6             for (Node<K, V> b = findPredecessor(key, cmp), n = b.next;;) { // b为key的前驱结点,n应该为key在跳表中的结点
     7                 Object v;
     8                 int c;
     9                 if (n == null) // n被删除,直接跳出
    10                     break outer;
    11                 Node<K, V> f = n.next;
    12                 if (n != b.next) // 如果数据不一致(有别的线程修改了其前驱结点的next域)
    13                     break; // 重新读取
    14                 if ((v = n.value) == null) { // n正在被删除
    15                     n.helpDelete(b, f); // 帮助删除
    16                     break; // 重新来过
    17                 }
    18                 if (b.value == null || v == n) // b已经被删除
    19                     break; // 重新来攻
    20                 if ((c = cpr(cmp, key, n.key)) == 0) // 找到结点
    21                     return n; // 返回
    22                 if (c < 0) // key所对应的结点不存在,b < x(key) < n, b -> n
    23                     break outer; // 退出外层循环
    24                 b = n; // 右移,继续
    25                 n = f;
    26             }
    27         }
    28         return null;
    29     }

    查找结点

    tryReduceLevel()

     1     private void tryReduceLevel() {
     2         HeadIndex<K, V> h = head;
     3         HeadIndex<K, V> d;
     4         HeadIndex<K, V> e;
     5         if (h.level > 3 // 层级大于3才考虑缩减层级
     6                 && (d = (HeadIndex<K, V>) h.down) != null // h是顶层,d是自顶层起第二层
     7                 && (e = (HeadIndex<K, V>) d.down) != null // e是第三层
     8                 && e.right == null 
     9                 && d.right == null 
    10                 && h.right == null // h, d, e三层索引均为空
    11                 && casHead(h, d) // 设置顶层头索引为d,即第二层
    12                 && h.right != null) // 如果h层又有了索引
    13             casHead(d, h); // 需要将顶层头索引再设置回来
    14     }

    从上往下连着三层为空,才尝试将顶层缩减掉,中途如果发现顶层又有索引了,还得把顶层加回来。

    buildFromSorted(SortedMap<K, ? extends V>)

     1     private void buildFromSorted(SortedMap<K, ? extends V> map) {
     2         if (map == null)
     3             throw new NullPointerException();
     4 
     5         HeadIndex<K, V> h = head;
     6         Node<K, V> basepred = h.node;
     7 
     8         ArrayList<Index<K, V>> preds = new ArrayList<Index<K, V>>(); // 保存各层的索引,每层最右边的索引
     9 
    10         for (int i = 0; i <= h.level; ++i)
    11             preds.add(null);
    12         Index<K, V> q = h;
    13         for (int i = h.level; i > 0; --i) { // 初始是各层头索引
    14             preds.set(i, q);
    15             q = q.down;
    16         }
    17 
    18         Iterator<? extends Map.Entry<? extends K, ? extends V>> it = map.entrySet().iterator();
    19         while (it.hasNext()) { // 根据传入的map的元素顺序依次添加
    20             Map.Entry<? extends K, ? extends V> e = it.next();
    21             int rnd = ThreadLocalRandom.current().nextInt();
    22             int j = 0;
    23             if ((rnd & 0x80000001) == 0) { // 同doPut方法
    24                 do {
    25                     ++j;
    26                 } while (((rnd >>>= 1) & 1) != 0);
    27                 if (j > h.level)
    28                     j = h.level + 1;
    29             }
    30             K k = e.getKey();
    31             V v = e.getValue();
    32             if (k == null || v == null)
    33                 throw new NullPointerException();
    34             Node<K, V> z = new Node<K, V>(k, v, null);
    35             basepred.next = z;
    36             basepred = z;
    37             if (j > 0) {
    38                 Index<K, V> idx = null;
    39                 for (int i = 1; i <= j; ++i) {
    40                     idx = new Index<K, V>(z, idx, null);
    41                     if (i > h.level)
    42                         h = new HeadIndex<K, V>(h.node, h, idx, i);
    43 
    44                     if (i < preds.size()) {
    45                         preds.get(i).right = idx; // 添加新索引
    46                         preds.set(i, idx); // 只保存最右边的索引
    47                     } else
    48                         preds.add(idx);
    49                 }
    50             }
    51         }
    52         head = h;
    53     }

    根据传入的map的元素顺序,依次添加到跳表里,使用List保存各层索引(最右边),批量处理索引关系,由于只在构造方法或clone方法里调用,一来很难有并发问题,而来,初始时跳表应该为空,批量添加更合适。

    行文至此结束。

    尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_skip.html

  • 相关阅读:
    静态与动态数据分析的测试方法
    网址无法访问,显示服务器拒绝了请求
    stf浏览器端删除离线设备
    STF日志提示 Not found ; no service started
    通过adb命令获取apk的安装路径
    js页面打开方式
    windows和mac回退及撤销快捷键
    单元测试规范
    Git :fatal: refusing to merge unrelated histories解决
    mysql详解4:分组
  • 原文地址:https://www.cnblogs.com/aniao/p/aniao_skip.html
Copyright © 2020-2023  润新知