Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

2023-11-18

首先介绍一些乐观锁与悲观锁:

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁的一种实现方式-CAS(Compare and Swap 比较并交换):

锁存在的问题:

Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。这就是一种独占锁,独占锁其实就是一种悲观锁,所以可以说 synchronized 是悲观锁。

悲观锁机制存在以下问题:  

1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。

3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。

乐观锁:

乐观锁( Optimistic Locking)在上文已经说过了,其实就是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

上面提到的乐观锁的概念中其实已经阐述了它的具体实现细节:主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是 Compare and Swap ( CAS )。

CAS:CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。   

CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。

JAVA对CAS的支持:在JDK1.5 中新增 java.util.concurrent (J.U.C)就是建立在CAS之上的。相对于对于 synchronized 这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。

以 java.util.concurrent 中的 AtomicInteger 为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解 getAndIncrement 方法,该方法的作用相当于 ++i 操作。

640?wx_fmt=png

在没有锁的机制下,字段value要借助volatile原语,保证线程间的数据是可见性。这样在获取变量的值的时候才能直接读取。然后来看看 ++i 是怎么做到的。getAndIncrement 采用了CAS操作,每次从内存中读取数据然后将此数据和 +1 后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而 compareAndSet 利用JNI(Java Native Interface)来完成CPU指令的操作:

640?wx_fmt=png

那么比较this == expect,替换this = update,compareAndSwapInt实现这两个步骤的原子性呢? 参考CAS的原理

CAS原理:CAS通过调用JNI的代码实现的。而compareAndSwapInt就是借助C来调用CPU底层指令实现的。下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:

640?wx_fmt=png

如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。

CAS缺点:

1. ABA问题:比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。如下所示:

640?wx_fmt=png

现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:head.compareAndSet(A,B);在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:

640?wx_fmt=png

此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:

640?wx_fmt=png

其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

640?wx_fmt=png

2. 循环时间长开销大:

自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3. 只能保证一个共享变量的原子操作:

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

CAS与Synchronized的使用情景:   

1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

补充:synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

concurrent包的实现:

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

1. A线程写volatile变量,随后B线程读这个volatile变量。

2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。

3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。

4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

1. 首先,声明共享变量为volatile;  

2. 然后,使用CAS的原子条件更新来实现线程之间的同步;

3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

640?wx_fmt=png

JVM中的CAS(堆中对象的分配): 

Java调用new object()会创建一个对象,这个对象会被分配到JVM的堆中。那么这个对象到底是怎么在堆中保存的呢?

首先,new object()执行的时候,这个对象需要多大的空间,其实是已经确定的,因为java中的各种数据类型,占用多大的空间都是固定的(对其原理不清楚的请自行Google)。那么接下来的工作就是在堆中找出那么一块空间用于存放这个对象。 

在单线程的情况下,一般有两种分配策略:

1. 指针碰撞:这种一般适用于内存是绝对规整的(内存是否规整取决于内存回收策略),分配空间的工作只是将指针像空闲内存一侧移动对象大小的距离即可。

2. 空闲列表:这种适用于内存非规整的情况,这种情况下JVM会维护一个内存列表,记录哪些内存区域是空闲的,大小是多少。给对象分配空间的时候去空闲列表里查询到合适的区域然后进行分配即可。

但是JVM不可能一直在单线程状态下运行,那样效率太差了。由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:

1. CAS:实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。

2. TLAB:如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。 

虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS 的相关文章

  • 38个MySQL数据库的小技巧

    1 如何快速掌握MySQL 培养兴趣 兴趣是最好的老师 不论学习什么知识 兴趣都可以极大地提高学习效率 当然学习MySQL 5 6也不例外 夯实基础 计算机领域的技术非常强调基础 刚开始学习可能还认识不到这一点 随着技术应用的深 入 只有有
  • java之MySQL数据库

    MySQL数据库 1 什么是数据库 答 数据库是以一定方式存储在一起 能予多个用户共享 具有尽可能小的冗余度 与应用程序彼此独立的数据集合 2 数据库的分类 具体含义 常见的数据库 答 关系型数据库和非关系型数据库 关系数据库 是建立在关系
  • springCloud - 第10篇 - 服务间调用追踪 (zipkin 的使用)

    前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到教程 一 在微服务系统中 不同应用服务可能会有各种不同的相互调用 springcloud 集成了 zipkin 来实现对于不同服务调用的追踪和统计
  • LIBSVM 使用

    预备 NTU TW Chih Chung Chang and Chih Jen Lin LIBSVM LIBSVM Data Classification Regression and Multi label 正文 a 编译libsvm u
  • 【机器学习】决策树 No.3

    1 决策树之信息论基础 决策树思想来源非常朴素 程序设计中的条件分支结构 if else 最早的决策树就是利用这类结构分割数据的一种分类学习方法 例 银行贷款例子 使用决策树划分是否贷款 此处特征为两个 房子 工作 香农 信息论创始人 19
  • 一文带你了解ES6迭代器(iterator)

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 一 迭代器 iterator 是什么 二 工作原理 总结 一 迭代器 iterator 是什么 迭代器 iterator 是一种接口 为各种不同的数据结构提供统一的
  • SkipList(跳表)

    跳表简介 跳表是基于有序链表实现的搜索结构 是一种动态的搜索结构 即支持动态插入和删除操作 且跳表查找和删除的平均时间复杂度是Olog n 因此跳表是一种时间复杂度相对较小的搜索结构 我们知道对一个数据集合的查找 最差的时间复杂度是O n
  • Switch / Xbox / PS / PSV / PSP 游戏指南

    1 Switch Xbox PS PSV PSP 对比分析 table 1 PSV2000 1000 比较分析 PSV2000 相对 PSV1000 具体变更点 薄了20 轻了15 屏幕改为液晶屏 PS键 开始键和选择键变成球形更方便按 内
  • 2,一个人体姿态识别的项目实现

    学习opencv的例子 1 认识 2 start 直接干货 例子1 例子2 例子3 例子4 例子5 固定阈值 自适应阈值 文档说明 参考链接 http codec wang opencv start 02 basic element ima
  • git仓库创建及上传

    如何创建一个git仓库并上传代码 1 在此之前我们需要安装git和注册gitee账号 1 打开git官网下载安装程序 然后按照指令进行安装即可 2 搜索gitee进行注册和绑定邮箱即可 上传代码时需要登录gitee账号 安装成功之后告诉本地
  • Android apk 反编译解析

    学习android开发 有必要对android的反编译有所了解 当然学习android反编译并不是为了破解别人的软件 完全是一种学习的方法 了解别人写程序的思想是一件很有趣的事情 工具下载地址 http code google com p
  • androidstudio开发!花三分钟看完这篇文章你就懂了!技术详细介绍

    阿里面试 自我介绍 首先要介绍自己的项目经验和个人的擅长点 因为面试官主要考察你的表达能力和语言精简能力 简历的编写其实这里可能需要注意几点 1 标题的直接按照姓名 几年工作经验 应聘岗位格式来填写 例如 黄铭 四年工作经验 Android
  • STP与RSTP区别

    STP 不能快速迁移 即使是在点对点链路或边缘端口 边缘端口指的是该端口直接与用户终端相连 而没有连接到其它设备或共享网段上 也必须等待2 倍的ForwardDelay 的时间延迟 端口才能迁移到转发状态 RSTP Rapid Spanni
  • Python 异步: 异步上下文管理器(17)

    动动发财的小手 点个赞吧 上下文管理器是一种 Python 构造 它提供了一个类似 try finally 的环境 具有一致的接口和方便的语法 例如通过 with 表达 它通常与资源一起使用 确保在我们完成资源后始终关闭或释放资源 无论资源
  • VC++ OpenCV+ZBar二维码识别

    利用OpenCV处理图像的优势 结合ZBar提高二维码识别结果 接口定义 include
  • SpringSecurity配置类--常用配置

    SpringSecurity配置类 在学习这门课的时候 实现各种功能时进行了各种配置 我想将各种配置综合讲述一下 首先自定义配置类 需要继承WebSecurityConfigurerAdapter这个类 在这个类里面做了一些默认配置 Con
  • linux grpc,grpc linux下的编译使用-Go语言中文社区

    1 一些工具安装 apt get install build essential autoconf libtool pkg config apt get install libgflags dev libgtest dev apt get
  • [转]IDEA 撤销 merge 操作(详解)

    目录 一 前言 二 解决方案 通过 Git Bash 命令行解决 1 合并过程中未发生冲突 2 合并过程中发生冲突 三 解决方案 通过 IDEA 解决 附带详细的操作图 1 合并过程中未发生冲突 2 合并过程中发生冲突 四 最后 作为一个开
  • 自己制作的 macOS Mojave 10.15.3 iso 文件,亲测可用(附 VMware15 安装 macOS Catalina 图文教程与 macO Catalina.iso 镜像下载地址)

    注 如果需要分享此资源 请注明原作者 不要把别人的东西当做自己的成果 也不要把别人免费分享出来的东西以有偿的方式去分享 自己制作的 macOS Mojave 10 15 3 iso 文件 亲测可用 已在 VMware 15 5 Player
  • AOSP 预置APP安装 以MicroG GmsCore.apk安装为例

    AOSP 预置APP 以MicroG为例 MicroG 无root安装教程 通过源码修改打开签名欺骗 预置不带源码的APP 其他 AOSP 安装谷歌三件套失败 开始寻找代替方案 尝试使用MicroG项目代替谷歌服务 目前情况 已成功安装 所

随机推荐

  • 第十八节 多核异构核间通信–ipcc

    由于MP157 是一款多核异构的芯片 其中既包含的高性能的A7 核及实时性强的M4 内核 那么这两种处理器在工作时 怎么互相协调配合呢 这就涉及到了核间通信的概念了 IPCC inter processor communication co
  • 【医学影像数据处理】2D/3D patch的crop和merge操作汇总

    在做3D分割任务中 多数的方法多采用整体缩放 或裁剪成一个个小的patch操作 这样做的一个主要原因是内存问题 相较于整体缩放 采用裁剪成patch的方法 对于小目标会更加的鲁棒 这也是大多数3D分割任务中常选取的方式 尤其是针对医学影像的
  • spring boot项目同时传递参数和文件的多种方式

    在开发接口中 遇到了需要同时接收参数和文件的情况 可以有多种方式实现文件 参数的接收 这里基于spring boot 3 vue 3 axios 做一个简单的代码演示 1 简单参数 文件参数 参数较少时 比较方便 直接参数接受即可 1 1
  • Mac上亲测好用的BlueStacks蓝叠安卓模拟器

    bluestacks mac是一款基于macOS系统打造的安卓模拟器 其优秀的稳定性 良好的兼容性一直是玩家的模拟器的首选哦 BlueStacks 4 for mac使Mac用户能够在他们的Mac上下载并享受他们喜欢的Android应用程序
  • vs2019运行asp.net framework(c# 基础) 排课系统的完整步骤

    前言 没想过有朝一日还会学net 毕竟java太强了 但因为特殊需要 还是学了一下net 发现她两真的好像呀 源码仓库 https gitee com web paul scheduling system 前期准备 vs2019 需要asp
  • Handler和Controller的区别

    以前一直以为这两个概念貌似是没有太大的区别 调研发现还是有一些区别的 Handler HandlerMapping接口实现从URL映射到请求处理程序bean 支持映射到bean实例和bean names Controller Base Co
  • 2021-08-10基于人脸识别的学生签到系统

    这是这学期web前端开发的大作业 因为要考研时间有限 很多功能只是先把页面做好了 没事实现和数据库的连接 用的数据大多数是在json中存储的 需求分析 一 教师端 功能需求 1登录 数据需求 用户名 密码 邮箱号 2能够管理课程 增加 删除
  • string16类型转string

    std string WChar2Ansi LPCWSTR pwszSrc int nLen WideCharToMultiByte CP ACP 0 pwszSrc 1 NULL 0 NULL NULL if nLen lt 0 retu
  • android 屏幕适配(亲测最兼容方便)

    Android屏幕适配有很多方式 1 设置屏幕固定尺寸适配 例如适配1280x720 只适合少量固定屏幕的设备 2 百分比布局 开发中多了很多代码 3 通过密度值px转dp来适配 方便兼容 基本准确 4 通过修改系统密度值适配 例如抖音适配
  • 【Linux】定时任务crontab/at

    在linux系统中定时任务常用两个命令crontab及at命令 两者区别在于crontab用于设置循环定时任务 即每隔一定时间或固定时间后启动对应任务命令 at用于设置一次性定时任务 在任务完成后定时任务即删除 1 crontab命令 1
  • 【送书活动】借助ChatGPT和Python,轻松实现办公自动化✨

    前言 作者主页 雪碧有白泡泡 个人网站 雪碧的个人网站 推荐专栏 java一站式服务 React从入门到精通 前端炫酷代码分享 从0到英雄 vue成神之路 uniapp 从构建到提升 从0到英雄 vue成神之路 解决算法 一个专栏就够了 架
  • malloc和strcpy,入门的指针面试题

    指针是C和C 编程语言中一个重要的概念 因此在面试以及工作中经常会涉及到指针相关的问题 现在列举几个比较基础问题 一 void getMemory char p p char malloc 100 void test void char s
  • 【使用Excel批量修改文件名称——超详细教程——无需复杂操作;bat,ren指令】

    基本指令 bat concatenate concatenate 该公式用于给新文件名名称添加后缀 ren公式 ren A2 B2 要注意的是ren后边有一个空格 后边有一个空格 此公式用于合并旧文件与新文件名称 操作步骤 首先创建一个文本
  • C语言 函数 下

    函数的定义 如果函数的定义在main函数之后 函数要在main函数前先进行声明 写一个函数完成两个整数的相加 int Add int a int b 函数的声明 int main printf 请您输入a b的值 int a 0 int b
  • Python入门---初识

    Python简介 Python的历史 1989年圣诞节 Guido von Rossum开始写Python语言的编译器 1991年2月 第一个Python编译器 同时也是解释器 诞生 它是用C语言实现的 后面 可以调用C语言的库函数 在最早
  • python入门教程(非常详细)

    Python是一种高级 解释性的脚本语言 其简单易学 灵活 强大等特点 使其成为了当代最流行的编程语言之一 如果您是想学习Python编程的新手 以下是详细的Python入门教程 以帮助您快速掌握Python编程基础 1 安装Python
  • STM32 基础系列教程 50 – MbedTls

    前言 mbed TLS 以前称为PolarSSL 是TLS和SSL协议的实现 并且需要相应的加密算法和支持代码 这是双重许可与Apache许可证 2 0版 与GPLv2许可也可 网站上指出 mbed TLS的目标是 易于理解 使用 集成和扩
  • 详解 C 语言中的弱符号与弱引用

    C语言中的 attribute weak 与 attribute weakref 引言 最近在看 linux 中一些驱动代码 驱动代码中为了实现程序的扩展性和兼容性用了很多 C 语言中的高级特性 本节就来谈一谈 C 语言中的弱符号和弱引用的
  • 机器学习在工程中使用要点

    机器学习现在有很多分支 大部分使用数据和模型优化入手 在此之前机器学习按照学派划分是有可靠的理论依据的 那么我们使用应该注意以下几点 1 如何获取训练集 一般我们下载现有的训练集为了学习或者来测试自己的算法泛化能力的表现水平 这时候我们对训
  • Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁 悲观锁 总是假设最坏的情况 每次去拿数据的时候都认为别人会修改 所以每次在拿数据的时候都会上锁 这样别人想拿这个数据就会阻塞直到它拿到锁 传统的关系型数据库里边就用到了很多这种锁机制 比如行锁 表锁等 读锁 写锁