ConcurrentSkipListMap内部是一种新的数据结构,可以叫它是跳跃链表结构,其节点相当于是excel中的表格,用了存储空间换取操作时间的,其查询时间复杂度是log(n)。另外插入时已做好节点的排序,遍历的时候就是排好序的了。
ConcurrentSkipListMap是可以并发操作的map,其key和value不能为null。其插入操作会比较耗性能,可以用在并发环境,插入少查询多的场景。
下面是它的结构示意图:
一、基本代码结构
从下面代码中看到,链表中新增加了一个Index对象,用来封装Node对象,其向下向右指向其他Index对象,构建起了跳跃链表。另外其实现了ConcurrentNavigableMap接口,这里不对这个导航maq做介绍了。还有Maq接口是没有继承Iterable接口,所有也不用实现迭代器方法。
public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable {
// 基础头节点对象,用来做初始化
private static final Object BASE_HEADER = new Object();
/** 头节点 */
private transient volatile HeadIndex<K,V> head;
/** 比较器 */
final Comparator<? super K> comparator;
/** 基础节点,保存原始数据 */
static final class Node<K,V> {
final K key;
volatile Object value;
volatile Node<K,V> next;
}
/** 跳跃链表的节点,封装node节点 */
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
}
/** 无比较器的构造方法 */
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
/** 有比较器的构造方法 */
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
/** 初始化方法,head节点的level为1 */
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
null, null, 1);
}
}
二、put方法
put方法的实现在doPut方法里面,分3块代码构建纵向的index,可以理解是构建上图中值为119这个节点,下面结合代码分析。
public V put(K key, V value) {
// 值不能为空
if (value == null)
throw new NullPointerException();
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
Node<K,V> z; // added node
// key不能为空
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
// 第一步:构建底层的Node对象
outer: for (;;) {
/** findPredecessor方法可以找到底层node节点的插入位置,在其后面插入新节点。
node链表简单的一维链表,把所有的对象连接起来,只是在上层用了Index对象做了跳跃链表 */
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) {
Object v; int c;
Node<K,V> f = n.next;
// 读不一致,可能链表结构被改变了,重新遍历
if (n != b.next) // inconsistent read
break;
// value为空,说明节点被清除了
if ((v = n.value) == null) { // n is deleted
/** 帮助清除节点,后面有该方法的解释。
可能出现这样一种情况,前面线程做好了清除的标记,但是线程调度原因被挂起了,
我们在当前线程调用helpDelete就可以加速清除被删除的节点。 */
n.helpDelete(b, f);
break;
}
// value为空说明被清除了,value等于自己说明被标记清除,则退出内循环,重新遍历
if (b.value == null || v == n) // b is deleted
break;
// 再次检查插入位置是否正确,如果大于0则向后找插入位置
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
// 等于0说明链表中有key相等的节点,此时判断是否需要重新设置新的值
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
// cas失败,则重新遍历
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
// 创建新的node,并且插入链表
z = new Node<K,V>(key, value, n);
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}
// 产生一个线程独立的随机数
int rnd = ThreadLocalRandom.nextSecondarySeed();
// 随机判断是否需要新增跳跃链表的节点,不是每次插入都构建跳跃表,它是随机的
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
// 第二步:构建整个纵向的Index,其只做好了向下的指向
int level = 1, max;
// 构建跳跃表的level也是随机产生
while (((rnd >>>= 1) & 1) != 0)
++level;
Index<K,V> idx = null;
HeadIndex<K,V> h = head;
// level小于等于最大的level,则只做简单的纵向构建
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null);
}
// 如果level大于最大的max,则需要新增一级level,因为level是由int值的位数决定的,所以其最大level为32
else { // try to grow by one level
level = max + 1; // hold in array and later pick the one to use
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])new Index<?,?>[level+1];
// 遍历左右纵向的index构建,只做了向下的指向
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
h = head;
int oldLevel = h.level;
// 其他线程可能增加了level,此时就不做增加了
if (level <= oldLevel) // lost race to add level
break;
HeadIndex<K,V> newh = h;
Node<K,V> oldbase = h.node;
// 头节点向上增加一级level,其右指向为新增的节点,可以参考结构图的第一行节点图
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
// 设置新的头节点
if (casHead(h, newh)) {
h = newh;
// 后面构建向右指向从老的level开始
idx = idxs[level = oldLevel];
break;
}
}
}
// 第三步:设置整个纵向Index的向右指向,把跳跃链表连接起来
// 外循环遍历每一级的index链
splice: for (int insertionLevel = level;;) {
int j = h.level;
// 内循环找到当前level层的插入点,重新构建向右指向
for (Index<K,V> q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break splice;
if (r != null) {
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
// 如果发现value为null,则端口这个index节点
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) {
// link方法就是重新构建向右指向的方法,后面有介绍
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
findNode(key);
break splice;
}
if (--insertionLevel == 0)
break splice;
}
if (--j >= insertionLevel && j < level)
t = t.down;
q = q.down;
r = q.right;
}
}
}
return null;
}
/** 该方法需要调用两次才会把清除的节点从链表中断开,第一次做标记清除,第二次做断开,说是可以减少CAS操作,其实我也没很懂 */
void helpDelete(Node<K,V> b, Node<K,V> f) {
if (f == next && this == b.next) {
if (f == null || f.value != f) // not already marked
casNext(f, new Node<K,V>(f));
else
b.casNext(this, f.next);
}
}
final boolean link(Index<K,V> succ, Index<K,V> newSucc) {
Node<K,V> n = node;
// 新节点的右节点是老的右节点
newSucc.right = succ;
// 前驱的右节点为新节点
return n.value != null && casRight(succ, newSucc);
}
三、get方法
如果把以上put方法理解了,我们再来学习get方法就容易多了。简单来说get方法就是找到前驱节点,然后对比前驱的后继节点的key与给定的key是否一致。
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 (;;) {
// 轮询找到前驱节点
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
// 等于0,说明找到了key对应的节点,然后返回value
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
// 小于0说明没有key对应的节点
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}
四、remove方法
remove方法保证会把node的value值设置为null,然后尝试断开node和index链表。
public V remove(Object key) {
return doRemove(key, null);
}
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
Object v; int c;
if (n == null)
break outer;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
// 确保比较为0才会往下走
if ((c = cpr(cmp, key, n.key)) < 0)
break outer;
if (c > 0) {
b = n;
n = f;
continue;
}
// 进入这里说明匹配到了key
// remove方法传入的value为null,故不会进入该分支
if (value != null && !value.equals(v))
break outer;
// 把value设置为null
if (!n.casValue(v, null))
break;
// 如果断开node节点失败的话,会调用findNode方法,其会找到value为null的节点,并且尝试把它从链表中断开。
if (!n.appendMarker(f) || !b.casNext(n, f))
findNode(key); // retry via findNode
else {
// findPredecessor方法里面可以断开value为null的index节点
findPredecessor(key, cmp); // clean index
// 头节点的右边节点为null,则尝试减少level
if (head.right == null)
tryReduceLevel();
}
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
}
return null;
}
private void tryReduceLevel() {
HeadIndex<K,V> h = head;
HeadIndex<K,V> d;
HeadIndex<K,V> e;
// 大于3级才需要考虑较少level
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 回退
}
五、其他方法
1.size方法
从以下代码可以看到size方法没有加锁,它是从节点的第一个元素遍历,统计value不为null的节点数。所以该方法不是原子安全的
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;
}
final Node<K,V> findFirst() {
for (Node<K,V> b, n;;) {
if ((n = (b = head.node).next) == null)
return null;
if (n.value != null)
return n;
n.helpDelete(b, n.next);
}
}
- 判空方法
判断map是否为空,直接检查第一个节点是否为null即可
public boolean isEmpty() {
return findFirst() == null;
}
- 遍历方法
遍历我们直接看entrySet() 方法即可,其迭代器的实现在EntryIterator类,EntryIterator的父类设置next的时候调用了findFirst()方法。迭代器每次调用完next() 方法时会调用advance(),重新设置next的值。从这些代码我们知道,ConcurrentSkipListMap的遍历是有序的,但是不保证原子和安全性。
final class EntryIterator extends Iter<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
Node<K,V> n = next;
V v = nextValue;
advance();
return new AbstractMap.SimpleImmutableEntry<K,V>(n.key, v);
}
}
abstract class Iter<T> implements Iterator<T> {
/** the last node returned by next() */
Node<K,V> lastReturned;
/** the next node to return from next(); */
Node<K,V> next;
/** Cache of next value field to maintain weak consistency */
V nextValue;
/** Initializes ascending iterator for entire range. */
Iter() {
while ((next = findFirst()) != null) {
Object x = next.value;
if (x != null && x != next) {
@SuppressWarnings("unchecked") V vv = (V)x;
nextValue = vv;
break;
}
}
}
public final boolean hasNext() {
return next != null;
}
/** Advances next to higher entry. */
final void advance() {
if (next == null)
throw new NoSuchElementException();
lastReturned = next;
while ((next = next.next) != null) {
Object x = next.value;
if (x != null && x != next) {
@SuppressWarnings("unchecked") V vv = (V)x;
nextValue = vv;
break;
}
}
}
}
- clear方法
从下面方法中可以看到,clear没有加锁,不是原子性的。
public void clear() {
for (;;) {
Node<K,V> b, n;
HeadIndex<K,V> h = head, d = (HeadIndex<K,V>)h.down;
if (d != null)
// 首先把头节点降低到最低一级
casHead(h, d); // remove levels
else if ((b = h.node) != null && (n = b.next) != null) {
// 从最低一级的index链表逐个地把node的value节点设置为null,最后断开null为空的node节点
Node<K,V> f = n.next; // remove values
if (n == b.next) {
Object v = n.value;
if (v == null)
n.helpDelete(b, f);
else if (n.casValue(v, null) && n.appendMarker(f))
b.casNext(n, f);
}
}
else
break;
}
}
六、总结
从上面的代码分析中,我们可以总结出ConcurrentSkipListMap的特点:
- 以存储空间换取查询和排序的时间,是一种便宜的查找和排序算法。
- CAS自旋实现插入操作。
- size方法,判断方法,遍历方法等不是原子安全的。
- key和value不予许为空。
- 在并发环境,少插入,多查找,并希望遍历有序的场景下可考虑使用。