《Java集合》ConcurrentSkipListMap

news2024/11/20 23:26:01

目录

  • 数据结构
  • findPredecessor
  • doGet
  • doRemove
  • doPut
    • 新值插入底层
    • 创建新值的索引
    • 连接索引

数据结构

java源码中对ConcurrentSkipListMap的描述如下:

在这里插入图片描述

图中,第0层为具体的数据,第1层的每一个node都有两个子node,一个指向同层的右边,一个指向下一层,它的类结构图如下:

在这里插入图片描述

head: head是类型为Index的数据结构,里面包含了一个保存key-value值的Node、指向右边的索引right、指向下一层的索引down

对于跳跃表的分析主要是它的增删改查,其中增与改在doPut方法,因此本文主要分析类图中的doGet(), doPUT(), doRemove方法

以下面的图先看下跳跃表的具体结构
在这里插入图片描述

head索引:比普通的索引多一个level字段,记录层级

以图中的索引4为例,它的right为8,down为4

findPredecessor

再开始之前先介绍这个方法,因为后面的三个方法中都会用到它,它的作用就是寻找的目标值的前一个节点,以前面的图为例,假设要寻找9,则需先找到它的前一个索引8,具体的步骤如下:

在这里插入图片描述

具体源码如下:

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable {
    // 查找带查询key的左边范围起始值,次起始值必须是最底层的值
    private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
        // 1. 如果key为null则直接抛出异常
        if (key == null)
            throw new NullPointerException(); // don't postpone errors
        for (;;) { // 2. 外层循环的作用:当其他线程修改了数据,当前线程重试开始寻找头结点
            for (Index<K,V> q = head, r = q.right, d;;) {
                if (r != null) {
                    Node<K,V> n = r.node;
                    K k = n.key;
                    if (n.value == null) {
                        // 3. 如果n的value为null,则将q->right->right.right修改为q->right.right,将right从链表中删除
                        if (!q.unlink(r))
                            // 其他线程已经修改数据,跳到最外层循环从头开始重新寻找
                            break;
                        r = q.right; 
                        continue;
                    }
                    if (cpr(cmp, key, k) > 0) {
                        // 4. 如果待查询的key大于right的key,说明待查询的key在right的右边,则继续往右寻找
                        q = r;
                        r = r.right;
                        continue;
                    }
                }
                if ((d = q.down) == null)
                    // 6. 已经查询到最底层,返回最底层的值
                    return q.node;
                // 5. right的key大于等于待查询的key,说明带查询的key位于head的key与right的key之间,往下寻找
                q = d;
                r = d.right;
            }
        }
    }
}

doGet

假设要查找9,则查找路径如下红色路线图:

在这里插入图片描述

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements Co ncurrentNavigableMap<K,V>, Cloneable, Serializable {
    private V doGet(Object key) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
            // 1. 查找索引
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                Object v; int c;
                if (n == null)
                    // 2. 如果n为null说明跳表中没有key的值,退出循环
                    break outer;
                Node<K,V> f = n.next;
                if (n != b.next)                // inconsistent read
                    // 3. 另一线程已经修改数据,调到外层outer循环重新开始
                    break;
                if ((v = n.value) == null) {    // n is deleted
                    // 4. n已被另一线程删除,当前线程原子性参与删除, 调到外层outer循环重新开始
                    n.helpDelete(b, f);
                    break;
                }
                if (b.value == null || v == n)  // b is deleted
                    // 5. b已被另一线程删除,重新查找
                    break;
                if ((c = cpr(cmp, key, n.key)) == 0) {
                    // 7. 查找到值,返回value
                    @SuppressWarnings("unchecked") V vv = (V)v;
                    return vv;
                }
                if (c < 0)
                    // 8. 跳表中没有值,直接跳出循环
                    break outer;
                // 6. n的key小于带查询key,继续next向右查找。
                b = n;
                n = f;
            }
        }
        return null;
    }
}

doRemove

在这里插入图片描述

如图中,假设要删除节点8:

  1. 首先需要找到Node8
  2. 将Node8标志为删除
  3. 遍历删除所有Index8

这里回顾一下**ConcurrentSkipListMap<K, V>**的索引数据结构
在这里插入图片描述

在本例中有2两个Index8,这两个Index中的Node指向的是同一个值,也就是第2步中将Node8标记为删除,则所有的Index8中的Node都被标记为删除,这样一来第3步遍历的时候就会删除所有的Index8。看下源码

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements Co ncurrentNavigableMap<K,V>, Cloneable, Serializable {
    final V doRemove(Object key, Object value) {
        if (key == null)
            throw new NullPointerException();
        Comparator<? super K> cmp = comparator;
        outer: for (;;) {
            // 1. findPredecessor需找到底层中最接近的8的索引b(本例中为4), n = b.next
            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)
                    // 要删除的key不存在,退出
                    break outer;
                if (c > 0) {
                    // 要删除的key在n的右边,继续往右寻找
                    b = n;
                    n = f;
                    continue;
                }
                // 2. 找到Index8
                if (value != null && !value.equals(v))
                    // value不为null并且与key对应的值不匹配则不能删除
                    break outer;
                // 将n的value设置为null
                if (!n.casValue(v, null))
                    // 另一线程已经修改n的value,重新开始
                    break;
                // 设置删除标志,并将b->n->f修改为b->n
                if (!n.appendMarker(f) || !b.casNext(n, f))
                    // 3. findNode中遍历的时候同样会删除无用的Index
                    findNode(key);                  // retry via findNode
                else {
                    // 3. 将n所在的Index从跳表中删除,即findPredecessor的第3步
                    findPredecessor(key, cmp);      // clean index
                    if (head.right == null)
                        // 减少无用的层级
                        tryReduceLevel();
                }
                @SuppressWarnings("unchecked") V vv = (V)v;
                return vv;
            }
        }
        return null;
    }
}

doPut

假设要往跳表中插入7,则出现的结果可能有两种,如图:
在这里插入图片描述

  1. 第一种情况是层级不变,这里称为情况A
  2. 第二种是层级改变,这里称为情况B

至于层级增加或者不变是随机,接下来代码的时候详细分析,这里先把插入值的步骤理一下:

  1. 找到底层的插入的位置,将新值插入到底层

在这里插入图片描述

  1. 创建新值的层级索引
    在这里插入图片描述

  2. 将新值的层级索引与原层级的索引连接
    在这里插入图片描述

接下来按这个顺序分析源码

新值插入底层

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements Co ncurrentNavigableMap<K,V>, Cloneable, Serializable { 
    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 (;;) {
            // 1. 查找合适的索引Index并获取其节点Node
            for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
                if (n != null) {
                    Object v; int c;
                    // n的下一个节点f
                    Node<K,V> f = n.next;
                    if (n != b.next)               // inconsistent read
                        // n被另一个线程修改,重新开始
                        break;
                    if ((v = n.value) == null) {   // n is deleted
                        // n被另一个线程删除,重新开始
                        n.helpDelete(b, f);
                        break;
                    }
                    if (b.value == null || v == n) // b is deleted
                        // b被另一线程删除,重新开始
                        break;
                    if ((c = cpr(cmp, key, n.key)) > 0) {
                        // 2. 待插入的key大于n的key, 向右继续寻找插入点
                        b = n;
                        n = f;
                        continue;
                    }
                    if (c == 0) { // 待插入的key已经存在
                        // 3. 更新原值并返回值
                        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
                }

                // 4. 找到插入点,即n的前一个位置, 修改底层的链表, b->n 更新为 b->z->n,即将新节点z插入链表
                z = new Node<K,V>(key, value, n);
                if (!b.casNext(n, z))
                    // 另一线程已经更改,当前线程重新开始
                    break;         // restart if lost race to append to b
                // 5. 底层链表更新结束
                break outer;
            }
        }
    }
}

创建新值的索引

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements Co ncurrentNavigableMap<K,V>, Cloneable, Serializable { 
    private V doPut(K key, V value, boolean onlyIfAbsent) {
        Node<K,V> z;             // added node
        // ......
        // 1. 获取随机数,根据随机数二进制中从右往左连续1的个数决定链表新的level
        int rnd = ThreadLocalRandom.nextSecondarySeed();
        // 最高层级32层,最底层1层
        if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
            int level = 1, max;
            // 2. 计算新值需要创建索引的层级。举例子:如果是5,即101,则level为1;如果是7,即111,则level+2为3
            while (((rnd >>>= 1) & 1) != 0)
                ++level;
            Index<K,V> idx = null;
            HeadIndex<K,V> h = head;
            // 假设此时level为3, max为3, level<=max,则先创建新插入节点的索引,即情况A
            if (level <= (max = h.level)) {
                for (int i = 1; i <= level; ++i)
                    // 3. 创建新值每一层的索引
                    idx = new Index<K,V>(z, idx, null);
            }
            else { // try to grow by one level
                // 假设level为7, max为3,则level为4,及情况B,此种情况head也需要更新层数
                level = max + 1; // hold in array and later pick the one to use
                // 创建4层新插入节点的索引
                @SuppressWarnings("unchecked")Index<K,V>[] idxs =
                    (Index<K,V>[])new Index<?,?>[level+1];
                for (int i = 1; i <= level; ++i)
                    // 3. 创建新值每一层的索引
                    // 注意这里的z,每一层的node都为z,也就是doRemove中所说的。
                    idxs[i] = idx = new Index<K,V>(z, idx, null);
                for (;;) {
                    h = head;
                    int oldLevel = h.level;
                    if (level <= oldLevel) // lost race to add level
                        break;
                    HeadIndex<K,V> newh = h;
                    Node<K,V> oldbase = h.node;
                    // 补齐头结点,并将第四层的头结点的right直接指向新插入节点的索引
                    for (int j = oldLevel+1; j <= level; ++j)
                        newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                    // 将第四层的newh作为该跳表的head
                    if (casHead(h, newh)) {
                        h = newh;
                        idx = idxs[level = oldLevel];
                        break;
                    }
                }
            }
            // ......
        }
        return null;
    }
}

连接索引

public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
    implements Co ncurrentNavigableMap<K,V>, Cloneable, Serializable { 
    private V doPut(K key, V value, boolean onlyIfAbsent) {
        Node<K,V> z;             // added node
        // ......
        // 更新层级,添加索引
        int rnd = ThreadLocalRandom.nextSecondarySeed();
        // 最高层级32层,最底层1层
        if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
            // ......
            // find insertion points and splice in
            splice: for (int insertionLevel = level;;) {
                int j = h.level;
                for (Index<K,V> q = h, r = q.right, t = idx;;) {
                    if (q == null || t == null)
                        // 已经到底层,结束更新
                        break splice;
                    if (r != null) {
                        Node<K,V> n = r.node;
                        int c = cpr(cmp, key, n.key);
                        if (n.value == null) {
                            // n已经被标记,删除n
                            if (!q.unlink(r))
                                break;
                            r = q.right;
                            continue;
                        }
                        if (c > 0) {
                            // 新插入的key大于right的key,向右移动
                            q = r;
                            r = r.right;
                            continue;
                        }
                    }

                    if (j == insertionLevel) {
                        // 将q->r更新为q->t->r
                        if (!q.link(r, t))
                            // 另一线程已修改数据,重新开始
                            break; // restart
                        if (t.node.value == null) {
                            // t已被删除,通过findNode函数将key删除
                            findNode(key);
                            break splice;
                        }
                        // 已经是最底层,退出循环
                        if (--insertionLevel == 0)
                            break splice;
                    }

                    // 向下一层继续更新
                    if (--j >= insertionLevel && j < level)
                        t = t.down;
                    q = q.down;
                    r = q.right;
                }
            }
        }
        return null;
    }
}

至此,关于ConcurrentSkipListMap的分析到此结束,感谢阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/454087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kong(3):动态负载均衡实现

nginx下负载均衡配置 upstream tulingmall-product-upstream {server 192.168.65.190:8866 weight100;server 192.168.65.190:8867 weight100; } server {listen 80;location /pms/ {proxy_pass http://tulingmall-product-upstream;} } 通过 Kong Admin API 进行上述的负载均…

srm采购管理系统有那些功能

srm采购管理系统&#xff0c;是通过系统的手段对采购过程进行管理和控制&#xff0c;实现降低成本、提高效益、提高企业核心竞争力的目的。那么 srm采购管理系统有哪些功能呢&#xff1f; 计划管理 srm采购管理系统提供了各种物料需求计划的功能&#xff0c;以帮助企业制定并控…

前端项目实战:网易云静态页面——导航栏

文章目录 一、实现目标二、顶部实现&#xff08;背景为黑色部分&#xff09;1. 内容布局2. 左边部分网易云logo左边的列表列表元素高亮指向每个列表元素的小红色三角“下载客户端”后的hot标志 3. 右边部分登陆创作者中心搜索 三、底部实现&#xff08;背景为红色部分&#xff…

Echarts 项目演练(上)整体页面结构的构建

项目分辨率响应式创建 项目顶部信息条创建 页面主体创建 接项目搭建与初始化之后继续对项目进行部署工作 项目展示&#xff1a; 技术栈&#xff1a; 1. vue3.0vue-router4.0axios 2. flex 布局 3. LESS 4. rem 屏幕适配 5. echarts5.0 项目分辨率响应式创建 对插件…

arduino esp-01s开发环境配置(备忘)

很久没玩arduion了&#xff0c;前天一个网友提了一个问题要我帮忙&#xff0c;结果电脑重新做了系统&#xff0c;又要重新设置环境&#xff0c;结果忘记了&#xff0c;做个备忘&#xff0c;省得以后又要重新研究。 1、附加开发板管理器网址&#xff1a;http://arduino.esp8266…

L1-002 打印沙漏

L1-002 打印沙漏 分数 20 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“*”&#xff0c;要求按下列格式打印 ************ *****所谓“沙漏形状”&#xff0c;是指每行输出奇数个符号&#xff1b;各行符号中…

机器学习——为什么逻辑斯特回归(logistic regression)是线性模型

问&#xff1a;逻辑斯蒂回归是一种典型的线性回归模型。 答&#xff1a;正确。逻辑斯蒂回归是一种典型的线性回归模型。它通过将线性回归模型的输出结果映射到[0,1]区间内&#xff0c;表示某个事物发生的概率&#xff0c;从而适用于二分类问题。具体地说&#xff0c;它使用sig…

Flink CDC 在易车的应用实践

摘要&#xff1a;本文整理自易车数据平台负责人王林红&#xff0c;在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分&#xff1a; Flink 应用场景DTS 平台建设Flink CDC Hudi 应用实践未来规划 点击查看直播回放和演讲 PPT 一、Flink 应用场景 Flink…

Mybatis-Plus详解(新建maven项目、查询所有信息、打印SQL日志、实现CRUD(增删改查)、分页、条件查询且分页,前后端分离式开发)

Mybatis-Plus详解(新建maven项目、查询所有信息、打印SQL日志、实现CRUD(增删改查)、分页、条件查询且分页&#xff0c;前后端分离式开发) MyBatis-Plus(opens new window) (简称MP) 是一个MyBatis(opens new window)的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变…

【牛客网】最难的问题与因子个数

目录 一、编程题 1.最难的问题 2.因子个数 一、编程题 1.最难的问题 链接&#xff1a;最难的问题__牛客网 (nowcoder.com) NowCoder生活在充满危险和阴谋的年代。为了生存&#xff0c;他首次发明了密码&#xff0c;用于军队的消息传递。假设你是军团中的一名军官&#xff…

Linux网络服务之yum仓库

目录 一、yum仓库简介二. ftp搭建yum源三. 搭建国内在线源四. 本地源和在线yum同时使用五. 通过缓存的方式保存所下载的软件包六 . 制作yum仓库 一、yum仓库简介 yum是一个基于RPM包&#xff08;是Red-Hat Package Manager红帽软件包管理器的缩写&#xff09;构建的软件更新机…

TortoiseSVN使用-权限配置

文章目录 3.5 权限配置3.5.1 单一版本库权限配置3.5.2 多版本库共享配置 3.5 权限配置 3.5.1 单一版本库权限配置 ①要设置授权访问就需要创建用户&#xff0c;并为用户设定权限 ②打开授权访问的配置 [1]打开 D:\DevRepository\Subversion\ERP\conf\svnserve.conf [2]将第 …

Day953.以假设驱动为指引 -遗留系统现代化实战

以假设驱动为指引 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于以假设驱动为指引的内容。 很多人在做遗留系统现代化的时候呢&#xff0c;总觉得它是一个十分复杂的技术问题。 本来嘛&#xff0c;无论是代码的重构、架构的拆分&#xff0c;还是 DevOps 平台的搭…

2023年 团体程序设计天梯赛个人感悟及总结(附题解)——遗憾国三

今年也是第一次参加了天梯赛&#xff0c;在这里也写一下自己的一些赛前准备、比赛过程的一些问题&#xff0c;以及赛后的一些总结以及感悟叭&#xff01;首先赛前准备的话也不能说我准备的非常的充分吧&#xff0c;但是L2阶的题目我是真的刷的很猛很疯的呢&#xff0c;这样看来…

Python类的继承

一、类的继承 1、什么是继承 通过继承基类来得到基类的功能 所以我们把被继承的类称作父类或基类&#xff0c;继承者被称作子类 代码的重用 2、父&#xff08;基&#xff09;类与子类 子类拥有父类的所有属性和方法 父类不具备子类自有的属性和方法 3、继承的用法 定义…

vite+react+ts+mobx+antd+react-router-dom+sass+tailwindcss

写了Vue项目比较多了&#xff0c;最近想换一下react技术栈&#xff0c;锻炼自己的技术&#xff0c;废话不多说&#xff0c;开始创建项目吧&#xff0c;写这篇博客也只是记录我创建的过程&#xff0c;不通的版本难免有坑&#xff0c;欢迎一起分享讨论下! 1、npm create vite //…

【李老师云计算】Spark配置及Scala实现100个随机数找最大值

索引 前言1. Spark部署1.1 Spark下载1.2 解压Spark1.3 修改环境变量1.4 修改主机Spark配置文件1.4.1 slaves.template文件配置1.4.2 spark-env.sh.template文件配置 1.5 分享主机Spark到从机1.6 启动Spark集群(★重启后的操作)1.7 通过jps查看是否启动成功1.8 通过网页查看是否…

rk3568 适配摄像头 (mipi 单摄)

rk3568 适配摄像头 (mipi 单摄) MIPI CSI&#xff08;Mobile Industry Processor Interface Camera Serial Interface&#xff09;是一种用于移动设备的高速串行接口标准&#xff0c;用于连接图像传感器和图像处理器。MIPI CSI接口使用差分信号传输技术&#xff0c;将数据分为…

C/C++ 高精度(加减乘除)算法二进制优化

高级精度算法系列 第一章 简单实现 第二章 压位优化 第三章 二进制优化(本章) 文章目录 高级精度算法系列前言一、基本原理1、存储方式2、计算方式 二、关键实现1、整型转高精度数组&#xff08;二进制&#xff09;2、字符串转高精度数组&#xff08;二进制&#xff09;3、高精…

小程序进阶

1.1组件基础 自定义组件的结构与页面是一致的&#xff0c;即也包含有4个部分&#xff0c;分别为: .wxml 组件的布局结构 .js 组件的处理逻辑 .json 组件的配置文件 .wxstngs 组件的布局样式 1.1.1创建组件 通常将组件放到独立的目录components当中这个目录需要手动创建 …