java高并发之ConcurrentSkipListMap的那些事
注意:本文内容基于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的那些事的更多相关文章
- Java高并发之锁优化
本文主要讲并行优化的几种方式, 其结构如下: 锁优化 减少锁的持有时间 例如避免给整个方法加锁 public synchronized void syncMethod(){ othercode1(); ...
- java高并发之线程池
Java高并发之线程池详解 线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对 ...
- java高并发之锁的使用以及原理浅析
锁像synchronized同步块一样,是一种线程同步机制.让自Java 5开始,java.util.concurrent.locks包提供了另一种方式实现线程同步机制——Lock.那么问题来了既然都 ...
- 1.6 JAVA高并发之线程池
一.JAVA高级并发 1.5JDK之后引入高级并发特性,大多数的特性在java.util.concurrent 包中,是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发 ...
- Java高并发之无锁与Atomic源码分析
目录 CAS原理 AtomicInteger Unsafe AtomicReference AtomicStampedReference AtomicIntegerArray AtomicIntege ...
- Java高并发之线程池详解
线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升. ...
- Java高并发之设计模式
本文主要讲解几种常见并行模式, 具体目录结构如下图. 单例 单例是最常见的一种设计模式, 一般用于全局对象管理, 比如xml配置读写之类的. 一般分为懒汉式, 饿汉式. 懒汉式: 方法上加synchr ...
- Java高并发之线程基本操作
结合上一篇同步异步,这篇理解线程操作. 1.新建线程.不止thread和runnable,Callable和Future了解一下 package com.thread; import java.tex ...
- Java高并发之同步异步
1.概念理解: 2.同步的解决方案: 1).基于代码 synchronized 关键字 修饰普通方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁. 修饰静态方法:作用于当前类对象加锁,进入同 ...
随机推荐
- RPC原理及RPC实例分析(转)
出处:https://my.oschina.net/hosee/blog/711632 在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服 ...
- 在zabbix中实现发送带有图片的邮件和微信告警
1 python实现在4.2版本zabbix发送带有图片的报警邮件 我们通常收到的报警,都是文字,是把动作中的消息内容当成了正文参数传给脚本,然后邮件或者微信进行接收,往往只能看到当前值,无法直观的获 ...
- 手势仿QQ侧滑---秀清
// // SlideViewController.h // qqcehua // // Created by 张秀清 on 15/5/25. // Copyright (c) 2015年 张秀清. ...
- K8s多节点部署+负载均衡+keepalived ——囊萤映雪
K8s多节点部署+负载均衡+keepalived --囊萤映雪 1.多节点master2 部署 2.负载均衡部署+keepalived 1.多节点master2部署: #从master01节点上拷贝证 ...
- shell——trap捕捉信号(附信号表)
trap捕捉信号有三种形式 第一种:trap "commands" signal-list 当脚本收到signal-list清单内列出的信号时,trap命令执行双引号中的命令. 例 ...
- 一加6刷入kali nethunter
Installing Kali NetHunter On the OnePlus 6 准备工具: adb: https://jingyan.baidu.com/article/22fe7cedf67e ...
- 浅谈.net core如何使用EFCore为一个上下文注类型注入多个实例用于连接主从数据库
在很多一主多从数据库的场景下,很多开发同学为了复用DbContext往往采用创建一个包含所有DbSet<Model>父类通过继承派生出Write和ReadOnly类型来实现,其实可以通过命 ...
- 帆软报表(finereport) 组合地图 保持系列名和值居中
自定义JavaScript代码,使用HTML解析 function(){ var name = this.name; var total = '<div style="width:10 ...
- swoole错误“Uncaught Error: Class 'swoole_server' not found”的解决办法
如果你在执行swoole对应文件时,报下面的错误, PHP Fatal error: Uncaught Error: Class 'swoole_server' not found in /mnt/w ...
- 02 HTML标签
2. HTML标签 1. HTML简介 用户使用浏览器打开网页看到结果的过程就是:浏览器将服务端的文本文件(即网页文件)内容下载到本地,然后打开显示的过程. 而文本文件的文档结构只有空格和黄航两种组织 ...