首页 > 编程语言 >JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet

JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet

时间:2022-11-04 14:09:53浏览次数:72  
标签:Node JAVA ConcurrentSkipListSet value next break key ConcurrentSkipListMap null


ConcurrentSkipListMap其实是TreeMap的并发版本。TreeMap使用的是红黑树,并且按照key的顺序排序(自然顺序、自定义顺序),但是他是非线程安全的,如果在并发环境下,建议使用ConcurrentHashMap或者​​ConcurrentSkipListMap​​。

ConcurrentSkipListSet其实是TreeSet的并发版本。TreeSet底层使用红黑树,并且按照key的顺序排序(自然顺序、自定义顺序),但是他是非线程安全的,如果在并发环境下ConcurrentSkipListSet。

ConcurrentSkipListMap和ConcurrentSkipListSet底层使用跳表数据结构来实现,跳表全称叫做跳跃表,它是一个随机化的数据结构,可以被看做二叉树的一个变种,通过使用“跳跃式”查找的方式使得插入、读取数据时复杂度变成了O(logn)。它在性能上和红黑树,AVL树不相上下,但是跳表的原理非常简单,目前在Redis和LeveIDB中都有用到。

跳跃表是一种典型的“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。实现跳表的基础是保证元素有序。

结构图

JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet_跳跃表


从图中可以看到, 跳跃表主要由以下部分构成:

  • 表头(head):负责维护跳跃表的节点指针。
  • 跳跃表节点:保存着元素值,以及多个层。
  • 层:保存着指向其他元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了提高查找的效率,程序总是从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层次。
  • 表尾:全部由 NULL 组成,表示跳跃表的末尾。

ConcurrentSkipListMap源码分析

ConcurrentSkipListMap类图

JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet_跳跃表_02

数据节点

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;
}
...
}

层级节点

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;
}
...
}

查找节点

我们来看下查找9的流程:

head(4)->4(4)->4(3)->4(2)->7(2)->7(1)->9

JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet_跳跃表_03

ConcurrentSkipListMap.findPredecessor()

找到与key距离最近的一个点,如果没找到返回头结点

// 找到与key距离最近的一个点,如果没找到返回头结点
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
// 从头结点开始,q就是要找的节点,r是q同层级的右节点,d是q的下一个层级的节点
for (Index<K,V> q = head, r = q.right, d;;) {
if (r != null) {
// n是r的数据节点
Node<K,V> n = r.node;
K k = n.key;
if (n.value == null) {
if (!q.unlink(r))
break; // restart
r = q.right; // reread r
continue;
}
// 比较key的大小,key>k则q指q的右节点,继续下一次循环
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
// 表示已经到最底层了
if ((d = q.down) == null)
return q.node;
// 如果r==null || key<=k,则q指向q下一层级的节点
q = d;
r = d.right;
}
}
}

ConcurrentSkipListMap.get()

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;
if ((c = cpr(cmp, key, n.key)) == 0) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
if (c < 0)
break outer;
b = n;
n = f;
}
}
return null;
}

插入结点

明白了查找的原理后,插入、删除就容易理解了。为了保存跳表的有序性,所以分三步:查找合适位置——进行插入/删除——更新跳表指针,维护层级性。

JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet_跳跃表_04

ConcurrentSkipListMap.doPut()

private V doPut(K key, V value, boolean onlyIfAbsent) {
Node<K,V> z; // added node
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {
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;
if ((v = n.value) == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted
break;
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
break; // restart if lost race to replace value
}
// else c < 0; fall through
}

z = new Node<K,V>(key, value, n);
if (!b.casNext(n, z))
break; // restart if lost race to append to b
break outer;
}
}

删除结点

JAVA并发容器-ConcurrentSkipListMap,ConcurrentSkipListSet_跳跃表_05



标签:Node,JAVA,ConcurrentSkipListSet,value,next,break,key,ConcurrentSkipListMap,null
From: https://blog.51cto.com/u_15861563/5823695

相关文章

  • JAVA并发容器-写时复制容器
    写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将......
  • Java中的指令重排
    在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型:编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。......
  • JAVA并发容器-ConcurrentHashMap 1.7和1.8 源码解析
    HashMap是一个线程不安全的类,在并发情况下会产生很多问题,详情可以参考​​HashMap源码解析​​;HashTable是线程安全的类,但是它使用的是synchronized来保证线程安全,线程竞争......
  • Java中的锁
    锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。Lock和synchronized......
  • Elasticsearch 同时使用should和must 只有must生效,java代码解决方案
    ES中同时使用should和must导致只有must生效解决方案失效的原因就是must和should在一起使用会不生效,如果全部都是must是不影响的.加入一个字段需要有类似url=aor......
  • java 压缩图片
    FilesourceFile=newFile("F:\\1.png");FiletargetFile=newFile("F:\\2.png");Thumbnails.of(sourceFile).scale(0.3f).toFile(targetFile)......
  • JAVA CST时间 转换成Date
    格式化CST时间SimpleDateFormatsdf=newSimpleDateFormat("EEEMMMddHH:mm:sszzzyyyy",Locale.US);CST时间转换成字符串,实体中为date类型的toString()转换即......
  • Java线程状态详解
    Java的每个线程都具有自己的状态,Thread类中成员变量threadStatus存储了线程的状态: privatevolatileintthreadStatus=0; 在Thread类中也定义了状态的枚举,共六......
  • 【java技术总结】Java-9中List.of()和Arrays.asList()的区别及原因分析
    1.List.of()和Arrays.asList()的区别?List.of()不可以插入null,Arrays.asList()可以。List.of()生成的List不可以修改,Arrays.asList()可以。List.of()原数组修改不会影响......
  • 13. Java 面向对象编程
    Java面向对象编程Java的核心思想就是OOP1.初识面向对象面向过程&面向对象面向过程思想步骤清晰简单,第一步做什么,第二步做什么.....面对过程适合处理一些......