JUC源码解析ConcurrentSkipListMap
Posted 林城画序
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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以上是关于JUC源码解析ConcurrentSkipListMap的主要内容,如果未能解决你的问题,请参考以下文章