Redis底层数据结构之深入理解跳表(2)

        上一篇文章中我们详细讲述了跳表的增添、查找和修改的操作,这篇文章我们来讲解一下跳表在多线程并发时的安全问题。在Redis中,除了网络IO部分和大文件的后台复制涉及到多线程外,其余任务执行时全部都是单线程,这也就意味着在Redis中不存在跳表的多线程并发问题。但在Java程序开发中,我们可能也会使用到跳表,这时候就不得不考虑如何设计一个并发安全的调表结构。

        JDK中提供了一种支持并发安全的跳表结构ConcerrentSkipListMap可以直接使用。不过这篇文章我们来自己思考一下在设计并发安全的跳表时我们需要考虑哪些内容。

全局读写锁

        首先对于跳表的操作可以分为读操作和写操作两种。那么最简单粗暴的方法就是对整个跳表加读写锁。所有的写操作需要持有写锁才可以进行,所有的读操作可以同时持有多把读锁,来实现并发读取。不过这时候我们来考虑这样一个问题,对跳表加了全局读写锁,如果一个线程拿到了写锁,并且对跳表的操作时间很长,那么后边所有的写操作和读操作都会被阻塞,如果对跳表的大小、存储数据的大小和每次写操作的时间进行严格限制,全局加读写锁在写少读多的场景下还是有一定的可用性的。

        上面全局加读写锁虽然实现简单,但是有很大可能会造成线程间的阻塞等待,实用性并不高,而造成阻塞问题的根源是锁的颗粒度太大了,有没有一种方法可以将锁的颗粒度进行细化,这样就可以同时允许多个线程拿到不同的锁进行操作,降低线程阻塞的概率。

节点读写锁

        我们来思考这样一个问题,当我们对单向链表并发操作时,对于写操作(增加、删除),其实只需要执行一步操作,就是找到被操作节点的前一个节点,并将其next修改为被操作节点的后一个节点。这就意味着,如果我们在并发环境下对被操作节点的前一个节点进行加锁操作,就可以保证同一时间只有一个线程对被加锁节点后的节点进行操作。

        在对跳表的节点进行加锁时,我们还需要多考虑一点,由于新加入的节点的高度是随机的,可能会比当前跳表的高度要高,这个时候我们需要对跳表的头结点进行加锁,并修改跳表的最大高度。

        总的来说,其实我们在细化跳表中锁的颗粒度到节点上时,需要考虑两部分。第一是如果新加入的节点高度小于跳表原最大高度,这时就从上至下逐层取到操作节点的左边界节点的锁即可;第二是如果新加入的节点高度大于跳表原有最大高度,这时就需要持有跳表的头结点锁,然后再进行第一部分的操作。

查询操作

        查询操作的步骤就是从跳表的最高层head节点开始,从上往下逐层遍历,直到找到key值小于target并且最接近于target的左边界节点,然后进行层数下降。具体的流程如下图所示:

增添操作

        增加操作执行时,如果增加的节点已经存在了,则会更新为新值,如果不存在,则要将其插入跳表中。可以将增加操作分为以下四个步骤:

        ①首先检查要添加的节点是否存在。

        ②如果存在,则将旧值更新为新值。

        ③如果不存在,则将新节点插入跳表中。

        ④如果要插入节点,并且节点高度比原有跳表要高,则对跳表的高度进行扩容。

        上述步骤中的①、②、③在并发情况下需要是原子性的,来保证单一线程对跳表更改的有效性。具体的流程如下图所示:

删除操作

        删除操作的步骤就是查询到要删除的节点,将其每一层的前一个节点的指针进行变更,指向当前层被操作节点的后一个节点就可以了。具体的流程如下所示:

        不过删除操作存在一个问题,就是左边界这种策略对于删除和增添并发执行的时候会失效,可能会造成新增添的节点被误删。所以造执行删除操作时,还是采用全局锁的方式来对其和增添操作进行隔离。

        本文主要梳理了一下自己实现并发安全的跳表的一些思路,算是学完Redis底层数据结构和并发编程之后笔者的一些思考,如果有什么问题或者勘误,欢迎评论区留言。

        

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

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

相关文章

Go语言依赖管理与版本控制-《Go语言实战指南》

在现代软件开发中,项目的第三方依赖和版本控制扮演着至关重要的角色。Go 语言自 Go 1.11 引入 Modules(模块化管理)以来,已经实现了内建的依赖管理机制,彻底摆脱了传统 GOPATH 模式的限制。 本章将深入探讨如何使用 Go…

Appium+python自动化(十一)- 元素定位- 下

1、 List定位 List顾名思义就是一个列表,在python里面也有list这一个说法,如果你不是很理解什么是list,这里暂且理解为一个数组或者说一个集合。首先一个list是一个集合,那么他的个数也就成了不确定性,所以这里需要用复…

stress 服务器压力测试的工具学习

一、stress 工具介绍 tress 是一种工具,可以对符合 POSIX 标准的操作系统施加可配置数量的 CPU、内存、I/O 或磁盘压力,并报告其检测到的任何错误。 stress 不是一个基准测试。它是由系统管理员用来评估其系统扩展性的工具,由内核程序员用来…

不止抓请求:5种开发场景中的抓包组合策略(含 Charles 等工具)

很多开发者用抓包,只在“接口调不通”的时候。 但在复杂项目中,抓包早已不仅是调错工具,更是开发节奏提速器、协作问题解耦器、架构瓶颈探测器。 关键在于——不同场景下,你要用对方法、配对工具。 以下是我根据日常开发实战&a…

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…

计算机系统大作业——程序人生

计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 物联网工程 学   号 2022112820 班 级 2237301 学 生 孟宇航 指 导 教 师 吴 锐 计算机科学与技术学院 2024年…

〈软件安装管家软件目录〉▷Windows系统版

①装机常用 ☞压缩解压WinRAR7-ZIPBandZip360压缩☞文件工具EverythingOneCommander XYplorer ReNamer ☞卸载软件CCleanerIObitUninstallerUninstall toolGeekAutodesk卸载Adobe卸载Ashampoo☞驱动软件驱动人生&#xff08;离线版&#xff09;驱动精灵&#xff08;离线版&…

CentOS Stream 8 Unit network.service not found

一、问题现象 在 CentOS Stream 8 操作系统中&#xff0c;配置完静态IP 信息&#xff0c;想重启网络服务。 执行如下命令&#xff1a; systemctl restart network 提示信息如下&#xff1a; Failed to restart network.service: Unit network.service not found. 二、问题…

极空间z4pro配置gitea mysql,内网穿透

极空间z4pro配置gitea mysql等记录&#xff0c;内网穿透 1、mysql、gitea镜像下载&#xff0c;极空间不成功&#xff0c;先用自己电脑科学后下载镜像,拉取代码&#xff1a; docker pull --platform linux/amd64 gitea/gitea:1.23 docker pull --platform linux/amd64 mysql:5.…

[假面骑士] 龙骑浅谈

作为一个伪二次元的我&#xff0c;总感觉目前没有什么好番可追。结果某一天在小破站刷到了一个假面骑士相关的视频&#xff0c;我就突发奇想&#xff0c;要不把假面骑士补完算了。 搜了搜&#xff0c;版权全在企鹅哪儿&#xff0c;不想充&#xff0c;于是去找了某盘的资源。果…

F5 GSLB 最佳实践:如何手动将Wide IP 故障转移到另一个数据中心

下面简要介绍如何手动将 Wide IP(用于 DNS 负载均衡)故障转移到另一个数据中心,并提供一些最佳实践。假设您使用 F5 BIG-IP DNS(以前称为 GTM)管理一个 Wide IP,该 IP 引用位于不同数据中心的虚拟服务器 (VIP)。 典型的 GSLB (BIG-IP DNS) 设置 Wide IP:表示您想要全局负…

FART 脱壳某大厂 App + CodeItem 修复 dex + 反编译还原源码

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ FART 脱壳 fartthread 方法在 app 启动的时候&#xff08;ActivityThread&#xff09;开启 fart 线程&#xff0c;休眠 60 秒&#xff0c;等待 app 启动完成后…

在maven项目中 继续增加maven 项目

背景项目 基于若依项目 由于若依项目都是Maven项目有父子结构因此自己建项目 也需如此管理 添加子Maven项目 利用idea 自带工具 maven archetype 这里选 webapp 骨架 在这里构建自己的项目架子即可 将 这个架子加入到启动类中

网络攻防技术十四:入侵检测与网络欺骗

文章目录 一、入侵检测概述二、入侵系统的分类三、入侵检测的分析方法1、特征检测&#xff08;滥用检测、误用检测&#xff09;2、异常检测 四、Snort入侵检测系统五、网络欺诈技术1、蜜罐2、蜜网3、网络欺骗防御 六、简答题1. 入侵检测系统对防火墙的安全弥补作用主要体现在哪…

吴恩达MCP课程(5):mcp_chatbot_prompt_resource.py

前提条件&#xff1a; 1、吴恩达MCP课程&#xff08;5&#xff09;&#xff1a;research_server_prompt_resource.py 2、server_config_prompt_resource.json文件 {"mcpServers": {"filesystem": {"command": "npx","args"…

【Linux】Linux基础指令3

1. which指令 功能&#xff1a;搜索系统指定的命令 2. whereis指令 功能&#xff1a;⽤于找到程序的源、⼆进制⽂件或⼿册 3. grep指令 语法&#xff1a; grep [ 选项 ] 搜寻字符串 ⽂件 功能&#xff1a;在⽂件中搜索字符串&#xff0c;将找到的⾏打印出来 常⽤选项&…

李沐《动手学深度学习》d2l安装教程

文章目录 最新回答报错提醒安装对应版本安装C工具和Windows SDK 最新回答 安装旧版本即可 pip install d2l0.17.0 WARNING: Ignoring invalid distribution -pencv-python (e:\python3.10\lib\site-packages) Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple C…

CMake 为 Debug 版本的库或可执行文件添加 d 后缀

在使用 CMake 构建项目时,我们经常需要区分 Debug 和 Release 构建版本。一个常见的做法是为 Debug 版本的库或可执行文件添加后缀(如 d),例如 libmylibd.so 或 myappd.exe。 本文将介绍几种在 CMake 中实现为 Debug 版本自动添加 d 后缀的方法。 方法一:使用 CMAKE_DEBU…

echarts树状图与vue3

父组件 - 使用RadialTreeView <template><div class"page-container"><div class"header-title">美国产品图谱 (ECharts Radial Tree)</div><RadialTreeView :chart-data"radialData" background-color"#E6E6F…

C# 日志管理功能代码

一、功能概述 本应用通过 AsyncFileLogger 类提供了灵活的日志控制功能&#xff0c;可在运行时通过 UI 界面启用或禁用日志记录。日志系统具有以下特点&#xff1a; 可控制开关&#xff1a;通过按钮随时启用或禁用日志&#xff0c;无需重启应用异步写入&#xff1a;日志记录采…