注意:本文内容基于JDK11,不同版本会有差异

ConcurrentSkipListMap的结构

ConcurrentSkipListMap是以链表(自然排序)的形式进行数据存储的。即在类中通过定义Node内部类来存储单个节点的数据,通过Node中的next属性,来记录链表的下一个节点。同时会对Node中记录的数据创建多级索引。结构总体如图所示:

源码解析

本文以put方法来对ConcurrentSkipListMap进行解析。

ConcurrentSkipListMap会在put方法里调用doPut方法。所以doPut()才是最终的实现



以下动图为doPut方法的动态演示:



对于doPut方法的理解,可以通过调用ConcurrentSkipListMap的put方法。断点调试,配合说明进行理解加深

关键代码说明:

   /**
*
* @param key
* @param value
* @param onlyIfAbsent 如果已经存在,是否不进行插入(false就是进行插入,true就是不进行插入)
* @return
*/
private V doPut(K key, V value, boolean onlyIfAbsent){
if(key == null){
throw new NullPointerException();
}
//比较器
Comparator<? super K> cmp = comparator;
for(;;){
//头索引
Index<K,V> h;
Node<K,V> b;
//禁止重排序
VarHandle.acquireFence();
//级别
int levels = 0;
/**
* 在第一次进行put方法时,会对head进行一个初始化操作。head是ConcurrentSkipListMap类的入口。
* 因为是链表结构,所以所有的操作都需要从head开始
* 这里定义了一个null的Node节点,并且把这个Node赋值给Index(Index可以通过查看源码的内部类),即当前索引所对应的节点就是该Node节点
* 这里定义的Node不存储数据,仅仅是作为整个链表的开始,可以配合结构图进行理解
* compareAndSet 原子操作,保证高并发下的原子性。
*/
if( (h = head) == null){
//第一次初始化操作时会进入到这个if里
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{
/**
* 这里包含了两个循环
* while循环是对索引的横向查找,一直找到right为空或者需要插入的key小于已存在的key的索引的位置
* for循环则是进行纵向查找,即查找到多层索引中的最底层索引
* cpr()方法是对两个key的自然排序比较。本质上使用的是compareTo方法进行比较
*/
for (Index<K,V> q = h, r, d;;){
//通过while循环查找合适的索引位置横行查找
while((r = q.right) != null){
Node<K,V> p;
K k;
if((p = r.node) == null || (k = p.key) == null || p.val == null){
RIGHT.compareAndSet();
}
else if(cpr(cmp, key, k) > 0){
q = r;
}
else{
break;
}
}
if(( d = q.down) != null){
++levels;
q = d;
}
else{
b = q.node;
break;
}
}
}
if(b != null){
Node<K,V> z = null;
/**
* 这里通过for循环来查找插入点,即key的值需要大于插入点之前Node的key的值且小于插入点之后Node的key的值
*/
for (;;){
Node<K,V> n, p;
K k;
V v;
int c;
if( (n = b.next) == null){
if(b.key == null){
cpr(cmp, key, key);
}
c = -1;
}
else if((k = n.key) == null){
break;
}
else if((v = n.val) == null){
c = 1;
}
else if((c = cpr(cmp, key, k)) > 0){
//如果key > k
//那么将n对应的node赋值给b。也就是重置b,将下一个Node的对象赋值到当前的b上
//同时将1赋值给c,然后进入下一次循环
b = n;
}
else {
c = 1;
} //具体的插入操作就是在这实现的
if(c < 0 && NEXT.compareAndSet(b, n, p = new Node<K,V>(key, value, n))){
z = p;
//跳出本次循环
break;
}
} if(z != null){
//源码中使用ThreadLocalRandom.nextSecondarySeed()方法。
// 但是我们无法使用,所以用这个临时替代。保证不报错
int lr = ThreadLocalRandom.current().nextInt();
//1/4的概率添加索引
if((lr & 0x3) == 0 ){
int hr = ThreadLocalRandom.current().nextInt();
long rnd = hr << 32 | lr & 0xffffffffL;
//添加之前级别需要下降
int skips = levels;
Index<K,V> x = null;
//for循环表示,当前节点如果需要生成索引,那么需要根据索引的层级来判断生产多少层的索引
for(;;){
x = new Index<K,V>(z, x,null);
if (rnd >= 0L || --skips < 0){
break;
}
else{
rnd <<= 1;
}
}
//addIndices是具体索引生成的方法
//该方法返回boolean类型的数据,如果索引生成成功,那么返回true,如果索引插入失败,那么返回false。
//这个if判断是代表如果当前索引生成成功,那么在当前索引的基础上再生成上一级索引(对索引再生成一层索引)。
if(addIndices(h, skips, x, cmp) && skips < 0 && head == h){
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){ }
}
//元素技术进行+1操作
addCount(1L);
return null;
}
}
}
}

java高并发之ConcurrentSkipListMap的那些事的更多相关文章

  1. Java高并发之锁优化

    本文主要讲并行优化的几种方式, 其结构如下: 锁优化 减少锁的持有时间 例如避免给整个方法加锁 public synchronized void syncMethod(){ othercode1(); ...

  2. java高并发之线程池

    Java高并发之线程池详解   线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对 ...

  3. java高并发之锁的使用以及原理浅析

    锁像synchronized同步块一样,是一种线程同步机制.让自Java 5开始,java.util.concurrent.locks包提供了另一种方式实现线程同步机制——Lock.那么问题来了既然都 ...

  4. 1.6 JAVA高并发之线程池

    一.JAVA高级并发 1.5JDK之后引入高级并发特性,大多数的特性在java.util.concurrent 包中,是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发 ...

  5. Java高并发之无锁与Atomic源码分析

    目录 CAS原理 AtomicInteger Unsafe AtomicReference AtomicStampedReference AtomicIntegerArray AtomicIntege ...

  6. Java高并发之线程池详解

    线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升. ...

  7. Java高并发之设计模式

    本文主要讲解几种常见并行模式, 具体目录结构如下图. 单例 单例是最常见的一种设计模式, 一般用于全局对象管理, 比如xml配置读写之类的. 一般分为懒汉式, 饿汉式. 懒汉式: 方法上加synchr ...

  8. Java高并发之线程基本操作

    结合上一篇同步异步,这篇理解线程操作. 1.新建线程.不止thread和runnable,Callable和Future了解一下 package com.thread; import java.tex ...

  9. Java高并发之同步异步

    1.概念理解: 2.同步的解决方案: 1).基于代码 synchronized 关键字 修饰普通方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁. 修饰静态方法:作用于当前类对象加锁,进入同 ...

随机推荐

  1. iOS中处理时间的类

      登录|注册     sakulafly的专栏       目录视图 摘要视图 订阅 Markdown博文大赛清新开启    天天爱答题 一大波C币袭来    中国云计算大会演讲议题公布     大 ...

  2. MHA高可用配置及故障切换

    MHA高可用配置及故障切换 目录 MHA高可用配置及故障切换 一.案例概述 二.案例前置知识点 1. MHA概述 2. MHA的组成 (1)MHA Manager(管理节点) (2)MHA Node( ...

  3. 有手就行4——jenkins项目构建类型(自由风格,maven风格)

    有手就行4--构建Maven项目 Jenkins项目构建类型(1)-Jenkins构建的项目类型介绍 Jenkins项目构建类型(2)-自由风格项目构建 Jenkins项目构建类型(3)-Maven项 ...

  4. 《Effective Python》笔记——第3章 类与继承

    一.尽量用辅助类来维护程序的状态 如下,用字典存储简单数据 class SimpleGradebook(): def __init__(self): self.__grades = {} def ad ...

  5. 【HDU6662】Acesrc and Travel(树型Dp)

    题目链接 大意 给出一颗树,每个点上有一个权值\(A[i]\),有两个绝顶聪明的人甲和乙. 甲乙两人一起在树上轮流走,不能走之前经过的点.(甲乙时刻在一起) 甲先手,并可以确定起点.甲想要走过的点权之 ...

  6. 关于 BSGS 以及 ExBSGS 算法的理解

    BSGS 引入 求解关于\(X\)的方程, \[A^X\equiv B \pmod P \] 其中\(Gcd(A,P)=1\) 求解 我们令\(X=i*\sqrt{P}-j\),其中\(0<=i ...

  7. Java经典案例之用三种方法求1~100以内素数之和

    素数,不能被除了1和本身以外整除的数被称为素数.接下来我用三种方式求得1~100以内素数. 方式一 外层每循环一次,内层就计算出这个数有几个因子,我们都知道素数的因子只有两个,所以如果个数为2就加进总 ...

  8. 12、Linux基础--挂载磁盘步骤、流处理工具awk(正则 比较 逻辑 算数表达式 流程控制)

    笔记 1.晨考 1.用两种方法,实现将文件中的以# 开头的行把# 去掉 sed -r 's/^#//g' /etc/fstab cat /etc/fstab | tr -d '^#' 2.将文件中的H ...

  9. systemd配置文件填写了ExecStop=/usr/bin/kill -9 $MAINPID之后重启在messages发生了报错

    原因在于systemd模块需要增加自动化检测,检测有一项为检测messages日志内是否有systemd的failed 写了一个检测脚本,脚本的检测messages内容为/bin/cat /var/l ...

  10. 什么,有狗快跑!慢着,这次手把手教你怎么过安全狗!(sql注入篇)

    前言 在记忆里上次绕安全狗还是在上次,开开心心把自己之前绕过狗的payload拿出来,发现全部被拦截了,事情一下子就严肃起来了,这就开整. 环境 本次环境如下sqli-lab的sql注入靶场 网站安全 ...