(十三)并发集合——ConcurrentSkipListMap/Set

news2025/1/13 10:16:30

ConcurrentSkipListMap

ConcurrentSkipListMap与TreeMap对应,相当于线程安全的TreeMap,key有序,TreeMap基于红黑树,ConcurrentSkipListMap基于跳表。

无锁链表的问题

用跳表而不用红黑树是因为目前计算机领域还未找到一种高效的、作用在树上的、无锁的、增加和删除节点的办法。(Doug Lea原话)
一般的无锁队列、栈,都是只在队头、队尾进行CAS操作,通常不会有问题。如果在链表的中间进行插入或删除操作,按照通常的CAS做法,就会出现问题。

操作1:在节点10后面插入节点20。如下图所示,首先把节点20的next指针指向节点30,然后对节点10的next指针执行CAS操作,使其指向节点20。
操作2:删除节点10。如下图所示,只需把头节点的next指针,进行CAS操作到节点30。
但是,如果两个线程同时操作, 一个删除节点10,一个要在节点10后面插入节点20。并且这两个操作都各自是CAS的,此时就会出现问题。删除节点10,可能会同时把新插入的节点20也删除掉。
在删除节点10的时候,实际受到操作的是节点10的前驱,也就是头节点。节点10本身没有任何变化。所以往节点10后插入节点20的线程,并不知道节点10发生了什么(可能被删除)。

解决办法

第一步,把节点10的next指针,标记成删除,即软删除;
第二步,找机会,物理删除。
做标记之后,当线程再往节点10后面插入节点20的时候,便可以先进行判断,节点10是否已经被删除,从而避免在一个删除的节点10后面插入节点20。这个解决方法有一个关键点: “把节点10的next指针指向节点20(插入操作)”和“判断节点10本身是否已经删除(判断操作)”,必须是原子的,必须在1个CAS操作里面完成!

实现

Mark节点

记录标记节点10已经被删除,需要标记它的next字段。可以新造一个Marker节点,使节点10的next指针指向该Marker节点。这样,当向节点10的后面插入节点20的时候,就可以在插入的同时判断节点10的next指针是否执行了一个Marker节点,这两个操作可以在一个CAS操作里面完成。

跳表

结构

由于跳表由多层链表叠起来构成,所以解决了无锁链表的插入或删除问题,也就解决了跳表的一个关键问题。
跳表底层的节点按照从小到大的顺序排列。
ConcurrentSkipListMap中跳表的部分结构源码如下
//底层Node节点,所有的数据节点都在此链表上
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;
    }
    //删除清理逻辑,b,f分别是此节点的前驱,后继
    void helpDelete(Node<K,V> b, Node<K,V> f) {
        if (f == next && this == b.next) {
            if (f == null || f.value != f) // 如果f是null或者没被标记,此节点后继设为marker节点
                casNext(f, new Node<K,V>(f));
            else //否则让b的后继为f
                b.casNext(this, f.next);
        }
    }
}
//上层的索引节点
static class Index<K,V> {
    //不存储数据,指向Node
    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;
    }
    //CAS操作,本节点的后继节点由cmp替换为val
    final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
        return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
    }

    final boolean unlink(Index<K,V> succ) {
        return node.value != null && casRight(succ, succ.right);
    }
}
//index的头节点
static final class HeadIndex<K,V> extends Index<K,V> {
    //第几层
    final int level;
    HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
        super(node, down, right);
        this.level = level;
    }
}
//跳查表的最顶层节点,只需记住这一个节点
private transient volatile HeadIndex<K,V> head;

查找前驱节点

//找到节点的前驱
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
    if (key == null)
        throw new NullPointerException();
    for (;;) {
        //head为最顶层HeadIndex节点
        for (Index<K,V> q = head, r = q.right, d;;) {
            if (r != null) {
                //取当前索引指向的数据节点
                Node<K,V> n = r.node;
                K k = n.key;
                //如果r指向的数据节点的值为null
                if (n.value == null) {
                    if (!q.unlink(r)) // CAS移除索引节点r,如果失败则重新循环
                        break;           
                    r = q.right;// CAS成功则重新读取r
                    continue;
                }
                //比较大小,如果给定的key比当前索引指向的数据节点的key大,q和r都往后移动一位
                if (cpr(cmp, key, k) > 0) {
                    q = r;
                    r = r.right;
                    continue;
                }
            }
            //如果q是第一层索引,返回q指向的数据节点
            if ((d = q.down) == null)
                return q.node;
            //向下移动一层
            q = d;
            r = d.right;
        }
    }
}

static final int cpr(Comparator c, Object x, Object y) {
    return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y);
}

get方法

public V get(Object key) {
    return doGet(key);
}

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)                // 不一致读,重新循环
                break;
            if ((v = n.value) == null) {    // 如果n的值为null,相当于被删除,执行协助删除方法
                n.helpDelete(b, f);
                break;
            }
            if (b.value == null || v == n)  // 如果b被删除了,重新循环
                break;
            if ((c = cpr(cmp, key, n.key)) == 0) { //找到了,返回
                @SuppressWarnings("unchecked") V vv = (V)v;
                return vv;
            }
            if (c < 0) //没找到,跳出循环返回null
                break outer;
            //b和n后移一位,继续查找
            b = n;
            n = f;
        }
    }
    return null;
}

put方法

public V put(K key, V value) {
    if (value == null)
        throw new NullPointerException();
    return doPut(key, value, false);
}

private V doPut(K key, V value, boolean onlyIfAbsent) {
    Node<K,V> z;             // 要添加的节点
    if (key == null)
        throw new NullPointerException();
    Comparator<? super K> cmp = comparator;
    outer: for (;;) {
        // 首先找到小于key的前驱结点b,n作为key的后继节点
        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)               // 不一致读则重新外层循环
                    break;
                if ((v = n.value) == null) {   // 如果n被删除了,执行协助删除方法
                    n.helpDelete(b, f);
                    break;
                }
                if (b.value == null || v == n) // 如果b被删除了,则重新开始外层循环
                    break;
                if ((c = cpr(cmp, key, n.key)) > 0) {//待插入的key大于n节点的key,则把b和n向后移动一位
                    b = n;
                    n = f;
                    continue;
                }
                if (c == 0) {//如果key对应的节点存在,直接CAS操作修改value
                    if (onlyIfAbsent || n.casValue(v, value)) {
                        @SuppressWarnings("unchecked") V vv = (V)v;
                        return vv;
                    }
                    break; // 如果CAS失败则重新外层循环
                }
                // else c < 0; 给定key小于n的key则插入失败
            }

            z = new Node<K,V>(key, value, n);
            if (!b.casNext(n, z))// 将b的后继由n换成z,如果CAS失败则重新外层循环
                break;  
            //成功插入后结束外层死循环       
            break outer;
        }
    }
    //成功插入不存在的node会走到这里,判断是否需要给新插入的节点增加索引
    int rnd = ThreadLocalRandom.nextSecondarySeed();
    if ((rnd & 0x80000001) == 0) { 
        int level = 1, max;
        while (((rnd >>>= 1) & 1) != 0)
            ++level;
        Index<K,V> idx = null;
        HeadIndex<K,V> h = head;
        if (level <= (max = h.level)) {
            for (int i = 1; i <= level; ++i)
                idx = new Index<K,V>(z, idx, null);
        }
        else { 
            level = max + 1; 
            @SuppressWarnings("unchecked") Index<K,V>[] idxs =
                    (Index<K,V>[])new Index<?,?>[level+1];
            for (int i = 1; i <= level; ++i)
                idxs[i] = idx = new Index<K,V>(z, idx, null);
            for (;;) {
                h = head;
                int oldLevel = h.level;
                if (level <= oldLevel)
                    break;
                HeadIndex<K,V> newh = h;
                Node<K,V> oldbase = h.node;
                for (int j = oldLevel+1; j <= level; ++j)
                    newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j);
                if (casHead(h, newh)) {
                    h = newh;
                    idx = idxs[level = oldLevel];
                    break;
                }
            }
        }
        
        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) {
                        if (!q.unlink(r))
                            break;
                        r = q.right;
                        continue;
                    }
                    if (c > 0) {
                        q = r;
                        r = r.right;
                        continue;
                    }
                }

                if (j == insertionLevel) {
                    if (!q.link(r, t))
                        break; // restart
                    if (t.node.value == null) {
                        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;
}

remove方法

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;
    outer: for (;;) {
        // 首先找到小于key的前驱结点b
        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)                    // 不一致读重新循环
                break;
            if ((v = n.value) == null) {        // 如果n被删除了,执行协助删除方法
                n.helpDelete(b, f);
                break;
            }
            if (b.value == null || v == n)      // 如果b被删除了,则重新循环
                break;
            if ((c = cpr(cmp, key, n.key)) < 0)  //如果 key比n的key小,说明没找到key,则跳出死循环返回null
                break outer;
            if (c > 0) { //如果key比n的key大,b和n都向后移动一位
                b = n;
                n = f;
                continue;
            }
            if (value != null && !value.equals(v)) //如果key匹配了,但是值没有匹配,则也视为未找到,跳出循环返回null
                break outer;
            if (!n.casValue(v, null)) //删除的元素为n,先将值置为null,然后执行相关删除逻辑
                break;
            if (!n.appendMarker(f) || !b.casNext(n, f))//在n的后面加上Marker节点
                findNode(key);                  
            else {
                findPredecessor(key, cmp);  // 清除索引,如果head索引的后继为null,则降低索引层数
                if (head.right == null)
                    tryReduceLevel();
            }
            //返回删除的元素
            @SuppressWarnings("unchecked") V vv = (V)v;
            return vv;
        }
    }
    return null;
}

ConcurrentSkipListSet

内部实际上引用了ConcurrentSkipListMap,map的key存储Set元素的值,value无意义,几乎所有方法都委托给ConcurrentSkipListMap执行。

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

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

相关文章

LabVIEW测试和调试Web服务

LabVIEW测试和调试Web服务发布Web服务至终端前&#xff0c;需要测试HTTP方法VI是否按照预期与客户端进行通信。可直接从LabVIEW项目将Web服务置于调试服务器上&#xff0c;从而允许客户端发送请求至HTTP方法VI。调试服务器提供类似沙盒的环境。1. (Windows) 右键单击我的电脑下…

JVM- 第二章-类加载子系统

类加载子系统 2. 类加载子系统 2.1. 内存结构概述2.2. 类加载器与类的加载过程 加载阶段链接阶段初始化阶段 2.3. 类加载器分类 2.3.1. 虚拟机自带的加载器2.3.2. 用户自定义类加载器 2.4. ClassLoader的使用说明2.5. 双亲委派机制2.6. 其他 2. 类加载子系统 2.1. 内存结构…

nacos

文章目录1、认识和安装nacos&#xff08;单机安装&#xff09;1.1、下载安装包1.2、解压1.3、端口配置1.4、启动1.5、访问2、nacos快速入门2.1、在父工程中添加依赖2.2、注释掉服务中原有的eureka依赖2.3、添加nacos的客户端依赖2.4、修改配置文件2.5、启动并测试2.6、总结3、n…

19、Java并发 Java wait() 和 notify() 方法

大家有没有发现&#xff0c;其实 「 一文秒懂 」 系列讲述的都是多线程并发开发的问题。这个话题太大了&#xff0c;估计没有上百篇文章都解释不清楚。 本文&#xff0c;我们来讲解下 Java 并发中的基础的基础&#xff0c;核心的核心&#xff0c;Java 并发编程中的最基本的机制…

为什么 APISIX Ingress 是比 Ingress NGINX 更好的选择?

作者容鑫&#xff0c;API7.ai 云原生技术工程师&#xff0c;Apache APISIX Committer。 本文将会对比两个比较流行的 Ingress controller 实现&#xff0c;希望能对读者进行 Ingress controller 选型中有所帮助。 Ingress NGINX 是 Kubernetes 社区实现的 Ingress controller&a…

高通量代谢组学四路筛选法,揭秘“神药”二甲双胍延长寿命的机制

百趣代谢组学分享—研究背景 目前据统计中国糖尿病患者人数达9700万以上&#xff0c;数量达到世界第一。这其中2型糖尿病占到了90%以上。二甲双胍是目前治疗2型糖尿病的一线“明星”药物&#xff0c;因其较少出现低血糖和体重增加副作用而受到广大患者和医生的青睐。代谢组学文…

Replicate Brogaard Stock Volatility Decomposition

文章目录IntroductionData and SampleDownload DataClean DataExtract Estimation Unit and Set Global VariablesImplement Brogaard DecompositionEstimate VAR Coefficients, Matrix BBB, ϵt\epsilon_tϵt​, Σe\Sigma_eΣe​, and Σϵ\Sigma_\epsilonΣϵ​Estimate 15-…

常用的前端大屏 适配方案

方案实现方式优点缺点vm vh1.按照设计稿的尺寸&#xff0c;将px按比例计算转为vw和vh1.可以动态计算图表的宽高&#xff0c;字体等&#xff0c;灵活性较高 2.当屏幕比例跟 ui 稿不一致时&#xff0c;不会出现两边留白情况1.每个图表都需要单独做字体、间距、位移的适配&#xf…

【寒假每日一题】AcWing 4509. 归一化处理

目录 一、题目 1、原题链接 2、题目描述 二、解题报告 1、思路分析 2、时间复杂度 3、代码详解 三、知识风暴 1、cmath头文件相关函数 2、cout大法 一、题目 1、原题链接 4509. 归一化处理 - AcWing题库 2、题目描述 在机器学习中&#xff0c;对数据进行归一化处理…

【C++】list用法简单模拟实现

文章目录1. list的介绍及使用1.1 list基本概念1.2 list的构造1.3 list的迭代器使用1.4 list 赋值和交换1.5 list 插入和删除1.6 list容量大小操作1.7 list 数据存取2. list的模拟实现这次要模拟实现的类及其成员函数接口总览2.1 结点类的实现2.2 迭代器的模拟实现2.3 反向迭代器…

yolov1 论文精读 - You Only Look Once- Unified, Real-Time Object Detection-统一的实时目标检测

Abstract 我们提出了一种新的目标检测方法- YOLO。以前的目标检测工作重复利用分类器来完成检测任务。相反&#xff0c;我们将目标检测框架看作回归问题&#xff0c;从空间上分割边界框和相关的类别概率。单个神经网络在一次评估中直接从整个图像上预测边界框和类别概率。由于…

PDF体积太大怎么缩小?这两种方法轻松解决

在我们日常处理的文件中&#xff0c;PDF文件的体积已经算是比较小的文件了&#xff0c;但是随着工作时间增加&#xff0c;我们用到的PDF文件也越来越多&#xff0c;而且有些PDF文件的内容非常丰富&#xff0c;文件体积变得更大&#xff0c;这就不利于我们将文件传输给别人&…

人脸检测算法模型MTCNN

MTCNN,Multi-task convolutional neural network(多任务卷积神经网络),将人脸区域检测与人脸关键点检测放在了一起。总体可分为P-Net、R-Net、和O-Net三层网络结构。P-Net是快速生成候选窗口,R-Net进行高精度候选窗口的过滤和选择,O-Net是生成最终边界框和人脸关键点。该…

使用JDK的 keytool 生成JKS,修改查看JKS信息

什么是keytool keytool 是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书&#xff0c;在JDK 1.4以后的版本中都包含了这一工具&#xff0c;所以不用再上网去找keytool的安装&#xff0c;电脑如果安装有JDK1.4及以上&#xff0c;就可以直接使用。 第一步&…

TOOM舆情分析网络舆情监控平台研究现状

随着网络舆情迅速发展&#xff0c;国内的舆情监测行业也日渐完善&#xff0c;舆情监控平台在企业发展过程中发挥重要作用&#xff0c;但同样也是有问题存在的&#xff0c;接下来TOOM舆情分析网络舆情监控平台研究现状? 一、网络舆情监控平台 网络舆情监控平台是一种能够对网…

maven概述以及简单入门

目录 1、Maven概述 1.1、Maven是什么 1.2 依赖管理 1.3 maven管理资源存放地址 1.4 Maven的作用 2.Maven基础概念 2.1仓库概念 2.坐标概念 1、Maven概述 1.1、Maven是什么 在Javaweb开发中&#xff0c;需要使用大量的jar包&#xff0c;我们手动去导入&#xff1b; 如何…

Mask RCNN网络源码解读(Ⅵ) --- 自定义数据集读取:MS COCOPascal VOC

目录 1.如何在Mask R-CNN中读取有关COCO数据集的内容&#xff08;my_dataset_coco.py&#xff09; 1.1 CocoDetection类 1.1.1 初始化方法__init__ 1.1.2 __getitem__方法 1.1.3 parse_targets 2.如何在Mask R-CNN中读取有关Pascal VOC数据集的内容&#xff08;my_datas…

docker搭建 java web服务

安装 Docker 只需通过以下命令即可安装 Docker 软件&#xff1a; >> rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm >> yum -y install docker-io可使用以下命令&#xff0c;查看 Docker 是否安装成功&#xff1a; …

SpringMvc源码分析(一):启动tomcat服务器,加载DispatcherServlet并将DispatcherServlet纳入tomcat管理

SpringMvc是主流的MVC框架&#xff0c;它是基于Spring提供的web应用框架&#xff0c;该框架遵循servlet规范。该框架的作用是接收Servlet容器&#xff08;如Tomcat&#xff09;传递过来的请求并返回响应。SpringMvc的核心就是servlet实例&#xff0c;而这个servlet在spring中就…

IB地理科SL和HL课程的区别

今期我们会谈到IB地理科这一科目的标准级别&#xff08;StandardLevel&#xff0c;SL&#xff09;课程和高级级别&#xff08;HigherLevel&#xff0c;HL&#xff09;。 两课程的最大区别:试卷数目和题目数量的不同&#xff0c;但两者的教材内容和科目指引&#xff08;SubjectG…