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))。
源码分析:
- 在看内部结构之前,先对跳表这种数据结构有个感性的认识,贴个图:
注:图片来自https://en.wikipedia.org/wiki/Skip_list
ConcurrentSkipListMap源码中也提供了图形化的注释: Java代码
- * 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
- * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
可见,跳表结构中主要有3中节点:Head节点、Index节点和普通的Node节点。
- 看下源码中这些节点的结构表示:
- /**
- * 节点持有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 | …
- +——+ +——+ +——+
2.现在要删除节点n,那么首先要对n进行标记,如图:
- +——+ +——+ +——+ +——+
- | b |——>| n |—–>|marker|——>| f | …
- +——+ +——+ +——+ +——+
可见,要删除节点n,首先是往节点n后面追加一个标记节点。
3.接下来是删除步骤,直接将节点n和后面的标记节点一起删除,如图:
- +——+ +——+
- | b |———————————–>| f | …
- +——+ +——+
上面是普通节点,再看下Index节点和Head节点: Java代码
- /**
- * 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;
- }
- }
再看下ConcurrentSkipListMap中的结构: Java代码
- 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;
- }
- }
- }
大概描述一下put方法(里面的一些细节后面分析): 1.首先要找出当前节点(其实还没有节点,这里只是给定的key和value)在Node链表(注意这个链表是图中最下层的部分,也就是base-level)中的前驱节点。 2.然后从前驱节点往后找到要插入的位置,进行插入。 3.插入成功后,可能会生成一个层级。
这里看一下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);
- }
- }
其实就是如果指定了比较器,就使用比较器;没指定比较器,就使用Key的自然序(Key需要实现Comparable接口)。 Java代码
- 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就是最底层的节点,返回这个节点。
- }
- }
- }
上述过程其实可以简单的理解为,从最高层的头节点开始找,给定key大于当前节点,就往右找,否则就往下找,一直找到最底层链上的节点,这个节点就是给定key在base_level上的前驱节点。
上面的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;
- }
这段代码有些蛋疼…总之就是50%的几率返回0,25%的几率返回1,12.5%的几率返回2…最大返回31。
继续看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方法: 1.首先要根据给定的key找出在base_level链表中对应的前驱节点(从结构图的左上角往右或往下一路找过来),注意put方法使用的log(n)时间主要体现在这个过程,这个查找过程中会顺便帮忙推进一些节点的删除。 2.找到前驱节点后,然后从这个前驱节点往后找到要插入的位置(注意当前已经在base_level上,所以只需要往后找),这个查找过程中也会顺便帮忙推进一些节点的删除。。 3.找到了要插入的位置,尝试插入,如果竞争导致插入失败,返回到第1步重试;如果插入成功,接下来会随机生成一个level,如果这个level大于0,需要将插入的节点在垂直线上生成level(level<=maxLevel + 1)个Index节点。
- 分析完了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;
- }
可见get的过程大概是这样:从最左最高的头节点开始往右或者往下(通过key的比较)遍历,一直找到base_level,然后往后一直找到给定节点,找不到的话返回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;
- }
- }
此方法中会再次调用前面分析过的findNode方法来查找一个key对应的Node,注意方法中外侧还是包了一层无限循环,为的是避免由于竞争导致findNode方法返回的Node又是一个被删除的节点。
- 继续看下containsKey方法:
- public boolean containsKey(Object key) {
- return doGet(key) != null;
- }
containsKey实现非常简单,直接基于doGet方法来做。
- 接着看下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
- }
这个方法看起来有点绕,其实是说:如果最高的前三个HeadIndex都为null(当前看起来),那么就将level减少1层,其实就是将h(当前的head)设置为d(head的下一层),设置完成之后,还有检测h(之前的head)的right是否为null(因为可能刚才由于竞争的原因,导致看到h的right为null),如果这会儿又不是null,那么还得回退回来,再次将head设置为h。
- 再看下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;
- }
containsValue的实现也比较简答,就是先找到base_level链的第一个节点,然后一直往后找,比较value值。
看下上面用到的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;
- }
- }
ConcurrentMap API方法实现中使用到的方法前面都分析过了,可以参考下前面的分析。
- 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方法: Java代码
- 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方法: Java代码
- 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;
- }
- }
- }
这个方法整体流程其实和findNode类似,都是从head开始先找到base_level的上给定key的前驱节点,然后再往后找。区别是这里传入关系参数-EQ、LT、GT(GT在代码里面没直接用,而是通过没有LT来判断),我们可以通过lowerEntry方法来分析,lowerEntry方法中间接调用的findNear方法传入的是LT,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],所以代码会从findNear中的出口3(见注释)返回。 接着分析下floorEntry: Java代码
- public Map.Entry<K,V> floorEntry(K key) {
- return getNear(key, LT|EQ);
- }
floorEntry方法中间接调用的findNear方法传入的是LT|EQ,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],这里分两种情况:1.如果k<n,那么会从出口3退出,返回b;2.如果k=n,那么会从出口2退出(满足条件(c == 0 && (rel & EQ) != 0)),返回n。 继续分析下ceilingEntry: Java代码
- public Map.Entry<K,V> ceilingEntry(K key) {
- return getNear(key, GT|EQ);
- }
ceilingEntry方法中间接调用的findNear方法传入的是GT|EQ,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],这里分两种情况:1.如果k<n(k>b),那么会从出口2退出(满足条件(c < 0 && (rel & LT) == 0)),返回b;2.如果k=n,那么也会从出口2退出(满足条件(c == 0 && (rel & EQ) != 0)),返回n。 最后分析下higherEntry: Java代码
- public Map.Entry<K,V> higherEntry(K key) {
- return getNear(key, GT);
- }
higherEntry方法中间接调用的findNear方法传入的是GT,所以当在findNear方法中定位到目标节点n的时候,节点关系是这样的:[b->n->f],节点key和给定key的大小关系是这样的:[b<k<=n<f],这里分两种情况:1.如果k<n,那么会从出口2退出(满足条件(c < 0 && (rel & LT) == 0)),返回b;2.如果k=n,那么会进入下一次循环,关系变成这样:[b<n=k<f],这时会从出口2退出(满足条件(c < 0 && (rel & LT) == 0)),这时返回的是f。 当然,上面分析的所有方法,都会遇到在findNear中遇到n==null的可能,这时关系图如下[b<k-null],k一定大于b,所以只有传入LT,才可以返回b;否则都是null。而且如果b本身是BaseHead,也只能返回null。
- ConcurrentSkipListMap分析到这里,一些关键的地方已经分析到了,至于其他没覆盖到的方法,基本都是基于上面分析的方法或思路来实现的,这里就不一一分析了。
最后提一下,看这个类源码的时候,一定要注意几个方面:
1.注意数据结构,可以仔细理解上面给出的结构图,或者先了解下跳表的背景知识。
2.注意删除节点是一个标记删除的过程,分两步,记住这个过程;而且源码中好多方法在遍历的过程中都会帮助推进删除。
3.由于此类是一种无锁并发的实现,所以代码细节上会考虑各种竞争情况,导致代码比较复杂,所以阅读的时候也要考虑全面,仔细看注释,揣摩作者的意图。
- 最后,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;
- }
- …
- 原文地址:http://brokendreams.iteye.com/blog/2253955