目录
注意:本文转自 https://blog.csdn.net/anlian523/article/details/107123092
前言
ConcurrentSkipListMap是一个可以在高并发环境下执行的有序map容器,在单线程环境下我们应使用TreeMap,在低并发环境下我们可以使用Collections.synchronizedSortedMap包装TreeMap来得到一个线程安全的有序map。ConcurrentSkipListMap底层实现是一个SkipList跳表,简单的说就是一个稍微复杂一点的链表结构。
源码注释
* 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
* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
上面这段注释已经给出了跳表的基本样子,最底层是base层用来存储数据,其他层都是index层用来加快搜索。可见不管是base层还是index层,第一个节点实际上是不存储真实数据的,而是作为一个dummy节点存在。真正的节点只会在第二个节点开始存储。
这些链表中的基本思想是在删除时标记已删除节点的next指针,以避免与并发插入发生冲突,并且在遍历以跟踪三元组(前驱,节点,后继)时进行标记,以检测何时以及如何取消链接 这些已删除的节点。——可能看完还是有点不懂,我们继续。
删除节点的具体过程如下:
假设在base层中间有这么一段节点,我们想要删除n:
* +------+ +------+ +------+
* ... | b |------>| n |----->| f | ...
* +------+ +------+ +------+
将n的value置为null,代表它被标记为删除节点了。从此刻开始,后续的public操作都不会遍历经过n节点了,但正在进行中的插入和删除操作(它们没有检测到n的value为null),可能会去尝试修改n的后继。
在n和f之间插入一个marker,这个marker的key为null,value指向自己。从此刻开始,任何节点都不可能添加到n后面了。
* +------+ +------+ +------+ +------+
* ... | b |------>| n |----->|marker|------>| f | ...
* +------+ +------+ +------+ +------+
将b的next指向f,从此刻开始,任何遍历都不可能遇到n了,n将会被gc回收掉。
* +------+ +------+
* ... | b |----------------------------------->| f | ...
* +------+ +------+
一个节点的删除过程必须经过这三个步骤,而且必须以这个顺序执行,这就是删除节点过程的“状态机”。
术语
base层:最下面的层(第0层),节点类型为Node。
index层:第1层或以上,节点类型为Index。
node节点:base层的节点。
index节点:index层的节点。
headIndex节点:index层的头节点。
marker:不存储数据,只是为了删除时使用。
被标记为删除:Node的value为置为null。简称为“被标记”。
节点定义
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) {//正常构造node
this.key = key;
this.value = value;
this.next = next;
}
Node(Node<K,V> next) {//构造marker
this.key = null;
this.value = this;
this.next = next;
}
}
这个是base层的node节点的定义,是真正存储数据的层,也是跳表的最底层。node节点只有right指针。
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
this.node = node;
this.down = down;
this.right = right;
}
}
这个是index层的index节点的定义,用来跳跃寻找目标。index节点有right和down指针。
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;
}
}
一个跳表的结构可能如上图所示。虽然index节点里也有数字,但这只是为了方便看图,index其实并没有直接存储数据。如果单独拎出来一列的话,它是这样的:
构造器
//不指定Compartor的话,则key肯定是Comparable子类对象
public ConcurrentSkipListMap() {
this.comparator = null;
initialize();
}
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
initialize();
}
private void initialize() {
keySet = null;
entrySet = null;
values = null;
descendingMap = null;
// private static final Object BASE_HEADER = new Object();
// 下面new的node节点,作为base层的dummy节点存在
head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null),
null, null, 1);
}
可见初始的时候,只有base层和一层index层,而且这两层都只有一个dummy节点作为头节点。
get 查找操作
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;;) {//b作为被找到的前驱
Object v; int c;
if (n == null)//b的后继为null,说明寻找目标不存在
break outer;
Node<K,V> f = n.next;
// 此时三个node的关系应该是b--->n--->f
if (n != b.next) // 如果n已经不是b的后继,从头开始循环以执行单次表达式
break;
if ((v = n.value) == null) { // n的value为null代表n被删除
n.helpDelete(b, f); //进行base层的删除活动
break;
}
if (b.value == null || v == n) //如果b被标记或n是个marker,说明b被删除
break;
if ((c = cpr(cmp, key, n.key)) == 0) {//说明目标为找到,就是n
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)//说明目标不存在,因为 b <目标 < n
break outer;
//if (c > 0) 执行右移操作