基于跳表的并发安全map
ConcurrentSkipListMap以跳表为基础结构的map集合,并且同时支持并发操作,比ConcurrentHashMap最大的优势应该就是有序。
内部结构
ConcurrentSkipListMap 实现了ConcurrentNavigableMap 接口继承于NavigableMap ,NavigableMap 则继承于sortedMap接口SortedMap提供了一些根据键范围进行查找的功能,比如返回整个Map中 key最小/大的键、返回某个范围内的子Map视图等等。
NavigableMap 接口进一步扩展了SortedMap的功能,提供了根据指定Key返回最接近项、按升序/降序返回所有键的视图等功能
属性
@SuppressWarnings("serial") // Conditionally serializable
final Comparator<? super K> comparator;
/** 懒加载跳表最上层的节点 */
private transient Index<K,V> head;
/** 原子累加器计数元素的个数*/
private transient LongAdder adder;
/**为subSet headSet 等方法提供的集合,返回某一范围的跳表key构成的集合 */
private transient KeySet<K,V> keySet;
/**返回map的value构成的集合 */
private transient Values<K,V> values;
/**返回key-value 键值对的集合 */
private transient EntrySet<K,V> entrySet;
/** 返回key 值递减的map集合 */
private transient SubMap<K,V> descendingMap;
内部结构
ConcurrentSkipListMap 在jdk11的时候删除了HeadIndex这种类型
ConcurrentSkipListMap内部一共定义了2 种不同类型的结点,元素的增删改查都从最上层的head指针指向的结点开始:
普通的数据节点node节点保存key value , next 指针会形成单链表
static final class Node<K,V> {
final K key; // currently, never detached
V val;
Node<K,V> next;
Node(K key, V value, Node<K,V> next) {
this.key = key;
this.val = value;
this.next = next;
}
}
索引节点
代表跳表的层级
static final class Index<K,V> {
final Node<K,V> node; // currently, never detached
final Index<K,V> down;
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;
}
}
很清楚的看到起内部结构
jdk 文档给的样例
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
* +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
- 表头(head):负责维护跳跃表的节点指针。
- 普通的node 是index nodes 里属性,node是个局部单链表,这点和我们自定义的跳表略有差异
- index nodes的right 指针指向同级的index nodes
- down 指针指向下一层级的index nodes
- 每一层的indexNode的node基础节点均指向完整链表的某个位置,最底层维护者一个完整的单链表
- 用Node来保存数据并组成完整数据的链表,并且这个链表相对独立,并不参与构建跳表结构。
- 从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层次。
- 表尾:全部由 NULL 组成,表示跳跃表的末尾。
构造器
public ConcurrentSkipListMap() {
this.comparator = null;
}
public ConcurrentSkipListMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public ConcurrentSkipListMap(Map<? extends K, ? extends V> m) {
this.comparator = null;
putAll(m);
}
public ConcurrentSkipListMap(SortedMap<K, ? extends V> m) {
this.comparator = m.comparator();
buildFromSorted(m);
}
put
public V put(K key, V value) {
if (value == null)//value 不能为空
throw new NullPointerException();
return doPut(key, value, false);
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
if (key == null)// 感觉多余的,为啥不放在put里判断
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
for (;;) {//自旋,cas失败的线程仍然可以重新尝试
Index<K,V> h; Node<K,V> b;
//ConcurrentSkipListMap 没有使用volatile 关键字,使用varhandel句柄保证内存可见性,禁止指令重排
//加写屏障,保证写屏障之前的指令不会重排到写屏障之后
VarHandle.acquireFence();
int levels = 0; // 初始层级为0
if ((h = head) == null) { // 实例化head节点
Node<K,V> base = new Node<K,V>(null, null, null);
h = new Index<K,V>(base, null, null);//设置索引层级的头结点
b = (HEAD.compareAndSet(this, null, h)) ? base : null;
}
else {
for (Index<K,V> q = h, r, d;;) { // //每一层级都需要插入q 从上之下开始
while ((r = q.right) != null) {//index node存在右节点
Node<K,V> p; K k;
//如果当前p节点已经被其他线程给删除了,说明r indexNode不可以用了,用r的下一个indexNode替换
if ((p = r.node) == null || (k = p.key) == null ||
p.val == null)
RIGHT.compareAndSet(q, r, r.right);
//如果带插入的key,比当前index node 的node(node 节点是个链表的头节点后面的next节点已经排好序的直接比较头结点即可) 节点key值大。说明还要继续向右查找
else if (cpr(cmp, key, k) > 0)
q = r; //保存引用
else
break;//小于或者等于需要从下一level插入
}
if ((d = q.down) != null) {//当前层级的right =null了需要从down 层级下插入
++levels;
q = d;
}
else {
b = q.node;//找到了需要插入的node 的位置
break;
}
}
}
if (b != null) {//插入到node节点的单链表中
Node<K,V> z = null; // new node, if inserted
for (;;) { // find insertion point
Node<K,V> n, p; K k; V v; int c;
if ((n = b.next) == null) {//说明已经到该层级的尾部了
if (b.key == null) // if empty, type check key now
cpr(cmp, key, key);
c = -1;
}
//既然是并发容器需要考虑key被删除的情况,判断node链表第二个节点是否为空
else if ((k = n.key) == null)
break; // can't append; restart
else if ((v = n.val) == null) {//既然是并发容器需要考虑value被删除的情况
unlinkNode(b, n);//从链表移除n节点,b是前驱节点
c = 1;
}
else if ((c = cpr(cmp, key, k)) > 0)//如果当前key大继续沿着链表找
b = n;
//如果给定的值插入位置的key值相等则替换后续节点value,并返回后续节点的原值
//修改已存在的key的时候,是不会进行层数变化的
else if (c == 0 &&
(onlyIfAbsent || VAL.compareAndSet(n, v, value)))
return v;
//小于则插入到该位置的前面
if (c < 0 &&
NEXT.compareAndSet(b, n,
p = new Node<K,V>(key, value, n))) {
z = p;
break;
}
}
//ConcurrentSkipListMap的分层数是通过一个随机数生成算法来确定,判断是否需要增加层级,如果需要就在各层级中插入对应的Index结点。
/*
当该随机数的二进制最高位与最末位不为0的时候,put进该Map的数据只会在最底层链表中,不会在高层链表中构建节点。当该随机数的二进制最高位与最末位都为0的时候,且该随机数从二进制第二位开始向左有多少个1,就代表会在多少层高层链表中构建节点,当然超过原跳表的最高层只会增加一层。
*/
if (z != null) {
int lr = ThreadLocalRandom.nextSecondarySeed();//生成低位随机数
//0x是十六进制的前缀 0x3:3先转成十进制为3得到的再转成二进制0011即lr&0011 为true表示需要增加层级
if ((lr & 0x3) == 0) {
int hr = ThreadLocalRandom.nextSecondarySeed();//生成高位随机数
long rnd = ((long)hr << 32) | ((long)lr & 0xffffffffL);
int skips = levels; // levels to descend before add
Index<K,V> x = null;
for (;;) { // create at most 62 indices
x = new Index<K,V>(z, x, null);
if (rnd >= 0L || --skips < 0)
break;
else
rnd <<= 1;
}
if (addIndices(h, skips, x, cmp) && skips < 0 &&
head == h) { // try to add new level
Index<K,V> hx = new Index<K,V>(z, x, null);
Index<K,V> nh = new Index<K,V>(h.node, h, hx);
HEAD.compareAndSet(this, h, nh);
}
if (z.val == null) // deleted while adding indices
findPredecessor(key, cmp); // clean
}
addCount(1L);//计数+1
return null;
}
}
}
}
get
查找的时候从最顶层的head节点开始,key值和head 的node节点的第一个key进行比较(仅比较第一个即可,后面的均是排好序的),如果key值大则继续从当前级别向右查找,反之小则需要从当前node节点向下一级别查找,直至遍历到最底层的链表为止。
public V get(Object key) {
return doGet(key);
}
private V doGet(Object key) {
Index<K,V> q;
VarHandle.acquireFence();//读屏障,禁止指令重排
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
V result = null;
if ((q = head) != null) {//头结点存在
outer: for (Index<K,V> r, d;;) {//遍历层级
while ((r = q.right) != null) {//当前层级
Node<K,V> p; K k; V v; int c;
//如果当前p节点已经被其他线程给删除了,说明r indexNode不可以用了,用r的下一个indexNode替换
if ((p = r.node) == null || (k = p.key) == null ||
(v = p.val) == null)
RIGHT.compareAndSet(q, r, r.right);
else if ((c = cpr(cmp, key, k)) > 0)//查找的key比当前k大,需要继续查找
q = r;
else if (c == 0) {//相等找到直接返回value
result = v;
break outer;
}
else
break;//小于的后面不用比较了需要从下一个级别查找
}
if ((d = q.down) != null)//从下一级别level查找
q = d;
else {
//到这里说明q.down = null,到最底层了即level=1级别,沿着node链表继续查找,最底层没有那就真找不到了
Node<K,V> b, n;
if ((b = q.node) != null) {
while ((n = b.next) != null) {
V v; int c;
K k = n.key;
if ((v = n.val) == null || k == null ||
(c = cpr(cmp, key, k)) > 0)
b = n;
else {
if (c == 0)
result = v;
break;
}
}
}
break;
}
}
}
return result;
}
remove
每个Index都保存一个Node,每个竖向的链表都指向底层链表中同一个node引用,删除方法只要把node中的value设置为null,当其他线程遍历到持有这个Node的Index时发现value为null,就移除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;
V result = null;
Node<K,V> b;
// b指向“小于且最接近给定key”的Node结点(或底层链表头结点)
outer: while ((b = findPredecessor(key, cmp)) != null &&
result == null) {
for (;;) {
Node<K,V> n; K k; V v; int c;
if ((n = b.next) == null)
break outer;
else if ((k = n.key) == null) // n is deleted
break;
else if ((v = n.val) == null) //当前线程发现 n 的value为空则删除n
unlinkNode(b, n);//从链表删除n
else if ((c = cpr(cmp, key, k)) > 0)
b = n;
else if (c < 0)
break outer;
// 此时n指向查到的结点,value已经被修改,直接跳出
else if (value != null && !value.equals(v))
break outer;
//删除关键:把node n中的value设置为null,当其他线程遍历到持有这个Node的Index时发现value为null,就移除Index,以这种“懒删除”的方式进行,以提高并发效率
else if (VAL.compareAndSet(n, v, null)) {
result = v;
unlinkNode(b, n);//删除n结点
break; // loop to clean up
}
}
}
if (result != null) {
tryReduceLevel();//减少层级
addCount(-1L);//size -1
}
return result;
}