Jdk1.6 JUC源码解析(26)-ConcurrentSkipListMap、ConcurrentSkipListSet
作者:大飞
- ConcurrentSkipListMap是一种线程安全的有序的Map。一般我们使用有序Map,不要求线程安全的情况下,可以使用TreeMap,要求线程安全的话,就可以使用ConcurrentSkipListMap。
- ConcurrentSkipListMap内部的数据结构是SkipList(跳表),内部Entry顺序是由实现了Comparable的key或者构造时指定的Comparator来保证。和TreeMap一样,对ConcurrentSkipListMap中元素的put、get和remove等操作的平均时间复杂度也是O(log(n))。
- 在看内部结构之前,先对跳表这种数据结构有个感性的认识,贴个图:
* 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 * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
- 看下源码中这些节点的结构表示:
/** * 节点持有key和value,按顺序链接,单向链表。 * 中间可能会链接一些处于中间状态的标记节点。 * 链表头节点是一个哑(dummy)节点,可以通过head.node访问。 * value域之所以定义成Object(而不是E),是因为还要存放一些针对标记节点和头节点的特殊值(non-V) */ 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域指向自身, * 同时标记节点的key为null。key是否为null在一些地方可以用来 * 区分标记节点,但无法区分标记节点和base-level链表头节点, * 因为base-level链表头节点的key也是null。 */ Node(Node<K,V> next) { this.key = null; this.value = this; this.next = next; } /** Updater for casNext */ static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater = AtomicReferenceFieldUpdater.newUpdater (Node.class, Node.class, "next"); /** Updater for casValue */ static final AtomicReferenceFieldUpdater<Node, Object> valueUpdater = AtomicReferenceFieldUpdater.newUpdater (Node.class, Object.class, "value"); /** * compareAndSet value field */ boolean casValue(Object cmp, Object val) { return valueUpdater.compareAndSet(this, cmp, val); } /** * compareAndSet next field */ boolean casNext(Node<K,V> cmp, Node<K,V> val) { return nextUpdater.compareAndSet(this, cmp, val); } /** * 判断节点是否为标记节点。 */ boolean isMarker() { return value == this; } /** * 判断节点是否是base-level链表的头节点。 */ boolean isBaseHeader() { return value == BASE_HEADER; } /** * 尝试在当前节点后面追加一个删除标记节点。 */ boolean appendMarker(Node<K,V> f) { return casNext(f, new Node<K,V>(f)); } /** * 通过追加一个删除标记节点或移除一个标记节点来推进删除。 */ 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) // not already marked appendMarker(f); else b.casNext(this, f.next); } } /** * 获取合法的value值。 */ V getValidValue() { Object v = value; if (v == this || v == BASE_HEADER) //如果是标记节点或者base头节点,返回null。 return null; return (V)v; } /** * 为当前映射(Node)创建一个不变(不可修改)的快照。 * 如果没有合法值,返回null */ AbstractMap.SimpleImmutableEntry<K,V> createSnapshot() { V v = getValidValue(); if (v == null) return null; return new AbstractMap.SimpleImmutableEntry<K,V>(key, v); } }
1.假设当前节点为n,n的前驱节点为b,n的后继节点为f,如图:
+------+ +------+ +------+ ... | b |------>| n |----->| f | ... +------+ +------+ +------+
+------+ +------+ +------+ +------+ ... | b |------>| n |----->|marker|------>| f | ... +------+ +------+ +------+ +------+
3.接下来是删除步骤,直接将节点n和后面的标记节点一起删除,如图:
+------+ +------+ ... | b |----------------------------------->| f | ... +------+ +------+
上面是普通节点,再看下Index节点和Head节点:
/** * Index节点表示跳表的层级。 * 注意到Node和Index都有正向的指针,但是它们的类型和作用都不同, * 无法抽象到一个基类里面。 */ 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; } /** Updater for casRight */ static final AtomicReferenceFieldUpdater<Index, Index> rightUpdater = AtomicReferenceFieldUpdater.newUpdater (Index.class, Index.class, "right"); /** * compareAndSet right field */ final boolean casRight(Index<K,V> cmp, Index<K,V> val) { return rightUpdater.compareAndSet(this, cmp, val); } /** * 判断当前Index的Node节点是否被删除。 */ final boolean indexesDeletedNode() { return node.value == null; } /** * 尝试设置新的后继节点。 */ final boolean link(Index<K,V> succ, Index<K,V> newSucc) { Node<K,V> n = node; newSucc.right = succ; //需要先检测当前Index的Node是否被删除。 return n.value != null && casRight(succ, newSucc); } /** * 尝试设置后继节点(right)为后继的后继(越过后继节点) */ final boolean unlink(Index<K,V> succ) { return !indexesDeletedNode() && casRight(succ, succ.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; } }
public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V> implements ConcurrentNavigableMap<K,V>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -8627078645895051609L; /** * 用来生成种子的随机数生成器。 */ private static final Random seedGenerator = new Random(); /** * 用来定义base-level的头结点。 */ private static final Object BASE_HEADER = new Object(); /** * 跳表最高层的head index */ private transient volatile HeadIndex<K,V> head; /** * 比较器。如果没设置这个比较器,那么久用key的自然序来比较。 * @serial */ private final Comparator<? super K> comparator; /** * 随机种子,这里没有用volatile修饰,多个线程看到不同的值也没关系。 */ private transient int randomSeed;
- 大体了解了内部结构,接下来先从简单的构造方法入手分析:
public ConcurrentSkipListMap() { this.comparator = null; initialize(); } public ConcurrentSkipListMap(Comparator<? super K> comparator) { this.comparator = comparator; initialize(); }
两个构造方法除了指定比较器的区别外,都调用了initialize方法,看下这个方法:
/** * 初始化或重置内部状态。 */ final void initialize() { //将内部一些域置空。 keySet = null; entrySet = null; values = null; descendingMap = null; //生成随机种子,这个种子用来生成随机的Level。 randomSeed = seedGenerator.nextInt() | 0x0100; // 确保非0 //生成头节点,该节点value是BASE_HEADER,level是1。 head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null), null, null, 1); }
- 然后分析下put方法:
public V put(K key, V value) { if (value == null) throw new NullPointerException(); return doPut(key, value, false); } private V doPut(K kkey, V value, boolean onlyIfAbsent) { //将原本的key转化成一个可比较的key。 Comparable<? super K> key = comparable(kkey); for (;;) { //通过key找到要插入位置的前驱节点(注意这个节点在base_level上) Node<K,V> b = findPredecessor(key); Node<K,V> n = b.next; for (;;) { if (n != null) { Node<K,V> f = n.next; if (n != b.next) //检测一下,如果读取不一致,说明发生竞争,重试。 break;; Object v = n.value; if (v == null) { //节点n已经被删除了 n.helpDelete(b, f); //删除动作 break; //重试。 } if (v == n || b.value == null) //节点b被删除 break; //重试。 int c = key.compareTo(n.key); if (c > 0) { //如果c>0,说明当前的节点应该排在n的后面,所以从n后面继续比较。 b = n; n = f; continue; } if (c == 0) { //如果onlyIfAbsent为true,那么不进行替换; //否则需要覆盖旧值。 if (onlyIfAbsent || n.casValue(v, value)) return (V)v; else break; // 覆盖时竞争失败,重试。 } // else c < 0; fall through } //1.构造一个新节点。 Node<K,V> z = new Node<K,V>(kkey, value, n); //2.尝试插入b和n之间。 if (!b.casNext(n, z)) break; // 如果尝试插入失败,重试一次。 //插入成功后,随机生成一个层级。(这个level不会超过31) int level = randomLevel(); if (level > 0) //level大于0,插入index insertIndex(z, level); return null; } } }
这里看一下doPut方法中转化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; } static final class ComparableUsingComparator<K> implements Comparable<K> { final K actualKey; final Comparator<? super K> cmp; ComparableUsingComparator(K key, Comparator<? super K> cmp) { this.actualKey = key; this.cmp = cmp; } public int compareTo(K k2) { return cmp.compare(actualKey, k2); } }
private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) { // 记录下一个要添加的level,以防重试。 int insertionLevel = indexLevel; Comparable<? super K> key = comparable(idx.node.key); if (key == null) throw new NullPointerException(); // 和findPredecessor过程类似,只是在过程中会添加index节点。 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) { Node<K,V> n = r.node; // compare before deletion check avoids needing recheck int c = key.compareTo(n.key); if (n.value == null) { if (!q.unlink(r)) break; r = q.right; continue; } if (c > 0) { q = r; r = r.right; continue; } } if (j == insertionLevel) { // 这里还需要检查t节点是否被删除,如果t节点被删除,就不能插入。 if (t.indexesDeletedNode()) { findNode(key); // cleans up return; } //尝试将Index t插入q和q的right节点r之间。 if (!q.link(r, t)) break; // restart if (--insertionLevel == 0) { // 最后还要做一次删除检测。 if (t.indexesDeletedNode()) findNode(key); return; } } if (--j >= insertionLevel && j < indexLevel) t = t.down; q = q.down; r = q.right; } } }
看到上述put的第2步过程,可能会疑惑,这是一个一个往后找的,put的时间复杂度看起来更像O(n)啊,其实玄机就在findPredecessor方法里面,看下这个方法:
/** * 返回最底层(base-level)节点链中比给定key小(在“给定节点”左边)的节点, * 如果没找到,那么返回底层链的头节点。 * 在查找过程中会顺手删除帮助删除一点标记为删除的节点。 */ private Node<K,V> findPredecessor(Comparable<? super K> key) { if (key == null) throw new NullPointerException(); // don't postpone errors for (;;) { //将最高层的头节点赋给q。 Index<K,V> q = head; //将最高层头结点的右节点赋给r。 Index<K,V> r = q.right; for (;;) { if (r != null) { //如果r不为null,找到r中的数据节点n。 Node<K,V> n = r.node; K k = n.key; if (n.value == null) { //如果n已经被删除,那么尝试推进删除。 if (!q.unlink(r)) break; // 推进删除失败,重试。 r = q.right; // 再次读取q的头结点,因为上面删除成功后,q的右节点变了。 continue; } //n没被删除的话,和key进行比较。 if (key.compareTo(k) > 0) { //如果给定的key表示的节点在n后面的话,继续往后找。 q = r; r = r.right; continue; } } //如果r为null,那么往下找。 //获取q的下节点d Index<K,V> d = q.down; if (d != null) { //如果d不为null,将d赋给q,d的右节点赋给r,再次循环。 q = d; r = d.right; } else return q.node; //如果d为空,说明q就是最底层的节点,返回这个节点。 } } }
上面的doPut方法中,最后还有一个生成level index的部分,首先调用randomLevel得到一个level值,如果这个值大于0,就调用insertIndex生成一个index,先看下randomLevel:
/** * Returns a random level for inserting a new node. * Hardwired to k=1, p=0.5, max 31 (see above and * Pugh's "Skip List Cookbook", sec 3.4). * * This uses the simplest of the generators described in George * Marsaglia's "Xorshift RNGs" paper. This is not a high-quality * generator but is acceptable here. */ private int randomLevel() { int x = randomSeed; x ^= x << 13; x ^= x >>> 17; randomSeed = x ^= x << 5; if ((x & 0x8001) != 0) // test highest and lowest bits return 0; int level = 1; while (((x >>>= 1) & 1) != 0) ++level; return level; }
继续看insertIndex方法:
/** * 为给定的数据节点创建和添加Index节点。 */ private void insertIndex(Node<K,V> z, int level) { HeadIndex<K,V> h = head; int max = h.level; if (level <= max) { Index<K,V> idx = null; //如果level比当前的max level小,那么创建level个节点,纵向链接起来 //(level2的down节点指向level1、level3的down节点指向level2...) for (int i = 1; i <= level; ++i) idx = new Index<K,V>(z, idx, null); //添加index。 addIndex(idx, h, level); } else { //如果level比当前的max level大,添加level。 /* * 为了减小其他线程在tryReduceLevel方法中检测空level的干扰, * 新的level添加时右节点就已经初始化好了。它们被依次放到一个 * 数组里面,当创建新的head index时,会反向访问它们。 */ //level设置为max+1 level = max + 1; //建立一个长度为level的Index数组, Index<K,V>[] idxs = (Index<K,V>[])new Index[level+1]; Index<K,V> idx = null; //还是创建level个节点,纵向链接起来,同时将它们放入Index数组。 for (int i = 1; i <= level; ++i) idxs[i] = idx = new Index<K,V>(z, idx, null); HeadIndex<K,V> oldh; int k; for (;;) { oldh = head; int oldLevel = oldh.level; if (level <= oldLevel) { // 竞争失败,跳出。 k = level; break; } HeadIndex<K,V> newh = oldh; Node<K,V> oldbase = oldh.node; for (int j = oldLevel+1; j <= level; ++j) /* *这里创建新的HeadIndex,其数据节点为oldBase,down节点为 *之前的head,right节点为上面Index中level最高的节点,level为j */ newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j); //尝试将head设置为新创建的HeadIndex。 if (casHead(oldh, newh)) { k = oldLevel; break; } } //添加index。 addIndex(idxs[k], oldh, k); } }
再继续看下这个addIndex方法:
private void addIndex(Index<K,V> idx, HeadIndex<K,V> h, int indexLevel) { // 记录下一个要添加的level,以防重试。 int insertionLevel = indexLevel; Comparable<? super K> key = comparable(idx.node.key); if (key == null) throw new NullPointerException(); // 和findPredecessor过程类似,只是在过程中会添加index节点。 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) { Node<K,V> n = r.node; // compare before deletion check avoids needing recheck int c = key.compareTo(n.key); if (n.value == null) { if (!q.unlink(r)) break; r = q.right; continue; } if (c > 0) { q = r; r = r.right; continue; } } if (j == insertionLevel) { // 这里还需要检查t节点是否被删除,如果t节点被删除,就不能插入。 if (t.indexesDeletedNode()) { findNode(key); // cleans up return; } //尝试将Index t插入q和q的right节点r之间。 if (!q.link(r, t)) break; // restart if (--insertionLevel == 0) { // 最后还要做一次删除检测。 if (t.indexesDeletedNode()) findNode(key); return; } } if (--j >= insertionLevel && j < indexLevel) t = t.down; q = q.down; r = q.right; } } }
这个方法要做的事情其实就是:针对一个给定的节点和level值,将之前建立的从上到下的Index节点链接进来,如图:
当然方法有几个地方要检测当前的idx节点有没有被删除,如果有,要调用一个findNode来做调整,看下这个方法:
/** * 通过给定的key查找对应的数据节点,查找过程中会顺便清理一些 * 已经标记为删除的节点。 * 一些地方会调用这个方法,不是为了查找节点,而是为了使用清理 * 删除节点的这个"副作用"。 * * 下列情况出现时,会重新遍历: * * (1) 在读取了n的next域之后,n不再是b当前的后继节点了,这意味 * 着我们没有和之前保持一致的3节点(b->n->f)快照,所以无法 * 删除后续节点。 * * (2) 节点n的value域为null,说明n已经被删除。这种情况下,我们 * 先帮助推进n节点的删除,然后再重试。 * * (3) n是一个标记节点或者n的前驱节点的value域为null。意味着 * findPredecessor方法会返回一个被删除的节点。我们无法移 * 除这节点,因为无法确定它的前驱节点。所以再次调用findPredecessor * (findPredecessor方法中会处理这个情况)并返正确的前驱节点。 */ private Node<K,V> findNode(Comparable<? super K> key) { for (;;) { //找到key对应的在base_level上的前驱节点。 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) // 读取不一致,说明发生竞争,重试。 break; Object v = n.value; if (v == null) { // n被删除了,帮助推进n的删除,重试。 n.helpDelete(b, f); break; } if (v == n || b.value == null) // b被删除了,重试。 break; //开始比较 int c = key.compareTo(n.key); if (c == 0) return n; //找到对应节点。 if (c < 0) return null; b = n; n = f; } } }
- 分析完了put方法,再看下get方法:
public V get(Object key) { return doGet(key); } private V doGet(Object okey) { //转换成可比较的Key。 Comparable<? super K> key = comparable(okey); Node<K,V> bound = null; Index<K,V> q = head; Index<K,V> r = q.right; Node<K,V> n; K k; int c; for (;;) { Index<K,V> d; // 向右遍历,一直到null或者当前给定key(对应的节点)大的节点(bound) // 当前给定key对应的节点应该在bound的左边。 if (r != null && (n = r.node) != bound && (k = n.key) != null) { if ((c = key.compareTo(k)) > 0) { q = r; r = r.right; continue; } else if (c == 0) { Object v = n.value; return (v != null)? (V)v : getUsingFindNode(key); } else bound = n; } // 往下找。 if ((d = q.down) != null) { q = d; r = d.right; } else break; } // 现在到了base_level,往后找就可以了。 for (n = q.node.next; n != null; n = n.next) { if ((k = n.key) != null) { if ((c = key.compareTo(k)) == 0) { Object v = n.value; return (v != null)? (V)v : getUsingFindNode(key); } else if (c < 0) break; } } return null; }
代码中还会看到,如果找到了节点,还会判断节点上的value是否为null。如果不为null,直接返回这个value;如果为null,说明这个节点被删除了(正在删除过程中),那么需要调用一个getUsingFindNode方法,看下这个方法:
private V getUsingFindNode(Comparable<? super K> key) { for (;;) { Node<K,V> n = findNode(key); if (n == null) return null; Object v = n.value; if (v != null) return (V)v; } }
- 继续看下containsKey方法:
public boolean containsKey(Object key) { return doGet(key) != null; }
- 接着看下remove方法:
public V remove(Object key) { return doRemove(key, null); } final V doRemove(Object okey, Object value) { //转换成可比较的key Comparable<? super K> key = comparable(okey); for (;;) { //找到key在base_level链上的前驱节点。 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) // 读取不一致,重试。 break; Object v = n.value; if (v == null) { // 如果n被删除了,帮助推进删除,然后重试。 n.helpDelete(b, f); break; } if (v == n || b.value == null) // 如果b被删除了,重试。 break; int c = key.compareTo(n.key); if (c < 0) return null; //如果比找到的前驱节点的后继节点小,说明没有指定的key对应的节点,返回null。 if (c > 0) { //如果比找到的前驱节点的后继节点大,说明目标节点在这个节点后面,往后找。 b = n; n = f; continue; } if (value != null && !value.equals(v)) return null;//给定的value和链表中的value不一致,删除失败。 if (!n.casValue(v, null))//首先尝试将要删除的目标节点n的value置空。 break; //如果失败,说明发生竞争,重试。 /* * 如果上一步将n的value置空成功,接下来首先尝试将n的后面追加一个标记节点, * 成功的话,再尝试将n和标记节点一起移除,这两部有任何一步失败,都会调用 * findNode来完成删除(利用findNode方法的副作用) */ if (!n.appendMarker(f) || !b.casNext(n, f)) findNode(key); // Retry via findNode else { /* * 如果上面的n.appendMarker(f)和b.casNext(n, f)都调用成功, * 然后就会调用这个方法,注意这里其实也是使用这个方法的 * 副作用来删除节点n的Index节点。 */ findPredecessor(key); if (head.right == null) // 删除了一些Index之后,这里判断一下head的right节点是否为null, // 如果为null,说明最高层的Index链已经不存在数据,可以删掉了。 tryReduceLevel(); } return (V)v; } } }
看一下上面方法中最后调用的tryReduceLevel方法:
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 }
- 再看下containsValue方法:
public boolean containsValue(Object value) { if (value == null) throw new NullPointerException(); for (Node<K,V> n = findFirst(); n != null; n = n.next) { V v = n.getValidValue(); if (v != null && value.equals(v)) return true; } return false; }
看下上面用到的findFirst方法:
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; n.helpDelete(b, n.next); } }
就是找到head中node(BASE_HEADER节点)的next,有可能next节点被删除了,所以会做检测,删除的话,推进一下删除,然后继续获取。size和isEmpty也是基于这个方法实现的:
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; } public boolean isEmpty() { return findFirst() == null; }
- ConcurrentSkipListMap实现了ConcurrentMap接口,看下这些接口方法的实现:
public V putIfAbsent(K key, V value) { if (value == null) throw new NullPointerException(); return doPut(key, value, true); } public boolean remove(Object key, Object value) { if (key == null) throw new NullPointerException(); if (value == null) return false; return doRemove(key, value) != null; } public boolean replace(K key, V oldValue, V newValue) { if (oldValue == null || newValue == null) throw new NullPointerException(); Comparable<? super K> k = comparable(key); for (;;) { Node<K,V> n = findNode(k); if (n == null) return false; Object v = n.value; if (v != null) { if (!oldValue.equals(v)) return false; if (n.casValue(v, newValue)) return true; } } } public V replace(K key, V value) { if (value == null) throw new NullPointerException(); Comparable<? super K> k = comparable(key); for (;;) { Node<K,V> n = findNode(k); if (n == null) return null; Object v = n.value; if (v != null && n.casValue(v, value)) return (V)v; } }
- ConcurrentSkipListMap同样实现了SortedMap接口,看下相关接口方法的实现:
public K lastKey() { Node<K,V> n = findLast(); if (n == null) throw new NoSuchElementException(); return n.key; }
这里出现了一个findLast方法,之前没分析过,看下:
Node<K,V> findLast() { Index<K,V> q = head; for (;;) { Index<K,V> d, r; if ((r = q.right) != null) { if (r.indexesDeletedNode()) { //如果发现节点已经删除的Index,顺便移除。 q.unlink(r); q = head; // 重试。 } else q = r; //向右找 } else if ((d = q.down) != null) { q = d; //向下找 } else { //现在到了base_level链上,向后找。 Node<K,V> b = q.node; Node<K,V> n = b.next; for (;;) { if (n == null) //最后定位到节点后需要检测一下是不是baseHead return (b.isBaseHeader())? null : b; Node<K,V> f = n.next; // 读取不一致,有竞争发生,重试。 if (n != b.next) break; Object v = n.value; if (v == null) { // n节点被删除,重试。 n.helpDelete(b, f); break; } if (v == n || b.value == null) // b节点被删除,重试。 break; b = n; n = f; } q = head; // restart } } }
public Map.Entry<K,V> lowerEntry(K key) { return getNear(key, LT); }这个方法的意思是找到一个比给定key小的所有key里面最大的key对应的Entry,里面调用了getNear方法:
AbstractMap.SimpleImmutableEntry<K,V> getNear(K key, int rel) { for (;;) { Node<K,V> n = findNear(key, rel); if (n == null) return null; AbstractMap.SimpleImmutableEntry<K,V> e = n.createSnapshot(); if (e != null) return e; } }getNear方法里面首先通过findNear方法找到指定的Node,然后通过createSnapshot方法返回一个Entry,这个方法最开始的时候看到过。下面重点看下这个findNear方法:
private static final int EQ = 1; private static final int LT = 2; private static final int GT = 0; // Actually checked as !LT /** * Utility for ceiling, floor, lower, higher methods. * @param kkey the key * @param rel the relation -- OR'ed combination of EQ, LT, GT * @return nearest node fitting relation, or null if no such */ Node<K,V> findNear(K kkey, int rel) { Comparable<? super K> key = comparable(kkey); for (;;) { Node<K,V> b = findPredecessor(key); Node<K,V> n = b.next; for (;;) { if (n == null) return ((rel & LT) == 0 || b.isBaseHeader())? null : b; //出口1 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 && (rel & EQ) != 0) || (c < 0 && (rel & LT) == 0)) return n; //出口2 if ( c <= 0 && (rel & LT) != 0) return (b.isBaseHeader())? null : b; //出口3 b = n; n = f; } } }
public Map.Entry<K,V> floorEntry(K key) { return getNear(key, LT|EQ); }
public Map.Entry<K,V> ceilingEntry(K key) { return getNear(key, GT|EQ); }
public Map.Entry<K,V> higherEntry(K key) { return getNear(key, GT); }
- ConcurrentSkipListMap分析到这里,一些关键的地方已经分析到了,至于其他没覆盖到的方法,基本都是基于上面分析的方法或思路来实现的,这里就不一一分析了。
- 最后,ConcurrentSkipListSet基于ConcurrentSkipListMap实现的:
public class ConcurrentSkipListSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -2479143111061671589L; private final ConcurrentNavigableMap<E,Object> m; public ConcurrentSkipListSet() { m = new ConcurrentSkipListMap<E,Object>(); } ... public boolean add(E e) { return m.putIfAbsent(e, Boolean.TRUE) == null; } ...
ok,代码解析完毕!
相关推荐
aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-...
2部分: jdk-1.6-windows-64-01 jdk-1.6-windows-64-02
1. 解压缩"java-jdk1.6-jdk-6u45-windows-x64.zip"文件,这将释放出"jdk-6u45-windows-x64.exe"可执行文件。 2. 双击运行"jdk-6u45-windows-x64.exe",安装向导会引导你完成安装过程。通常,你需要选择安装路径,...
下载的压缩包文件"jdk-6u45-windows-x64(1.6 64).exe"是Windows 64位系统的安装程序。安装过程中,用户需要选择安装路径,并设置环境变量,包括`JAVA_HOME`指向JDK的安装目录,`PATH`添加JDK的bin目录,确保系统可以...
标题中的“jdk1.6集成jjwt的问题”指的是在Java Development Kit (JDK) 版本1.6的环境下,尝试整合JSON Web Token (JWT) 库jjwt时遇到的挑战。JWT是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 ...
三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3
标题中的"jdk-jdk1.6.0.24-windows-i586.exe"是一个Java Development Kit(JDK)的安装程序,适用于Windows操作系统且为32位版本。JDK是Oracle公司提供的一个用于开发和运行Java应用程序的软件包。这个特定的版本,...
1.okhttp3.8源码使用jdk1.6重新编译,已集成了okio,在javaweb项目中使用,未在安卓项目中使用 2.okhttp3.8源码使用jdk1.6重新编译_okhttp3.8.0-jdk1.6.jar
三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3
三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3
三部分: jdk-1.6-windows-32-1 jdk-1.6-windows-32-2 jdk-1.6-windows-32-3
### JDK1.6安装及与JDK-1.5版本共存 #### 一、前言 随着软件开发环境的变化和技术的进步,不同的项目可能需要不同的Java版本来支持其运行。例如,在某些特定环境下,可能既需要使用JDK1.5(Java Development Kit ...
logback-cfca-jdk1.6-3.1.0.0.jar
- 这可能是ZXing库的完整源码包,专门针对JDK1.6编译,包含了所有必要的源文件和资源,供开发者进行更深度的定制和集成。 总之,ZXing库是一个强大的条形码和二维码工具,这个特别适配JDK1.6的版本为那些仍在使用...
这个压缩包文件"jdk-6u45-linux-x64.zip"包含的是JDK 1.6.0_45(也被称为6u45或1.6u45)的64位Linux版本。JDK 1.6是Java平台标准版的一个重要版本,它提供了许多功能和性能改进,是许多企业级应用的基础。 JDK 1.6u...
jdk-1.6-linux-32-1 jdk-1.6-linux-32-2 jdk-1.6-linux-32-3
压缩包中的文件`jdk-6u45-windows-i586.exe`是JDK 1.6更新45的Windows 32位安装程序。安装步骤通常包括: 1. 下载并运行安装程序。 2. 遵循安装向导的提示,选择安装路径和组件。 3. 设置环境变量,包括`JAVA_HOME`...
java环境搭建 jdk6(包含jre)64位 jdk-6u45-windows-x64
Linux64位环境下的jdk6安装包:jdk-6u45-linux-x64.bin。 由于积分无法修改,现提供网盘下载地址: https://pan.baidu.com/s/1BE55ImTxZTQO6T22051P2g 提取码:5wvm
Java编程开发工具包,最新版本,很好用,经典