乐观锁、悲观锁、分布式锁

面试必备-行锁、表锁 - 乐观锁、悲观锁的区别和联系(史上最全)_yxg520s的博客-CSDN博客_行锁和表锁是悲观锁吗

乐观锁、悲观锁、读写锁、互斥锁之间的关系_Lerix的博客-CSDN博客_悲观锁和互斥锁区别

面试官灵魂4连问:乐观锁与悲观锁的概念、实现方式、场景、优缺点? - 知乎 (zhihu.com)

什么是分布式锁?实现分布式锁的三种方式 - 刘清政 - 博客园 (cnblogs.com)

[分布式锁看这篇就够了 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/42056183#:~:text=什么是分布式锁?,当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。)

相关名词

  • |–表级锁(锁定整个表)
  • |–页级锁(锁定一页)
  • |–行级锁(锁定一行)
  • |–共享锁(S锁,MyISAM 叫做读锁)
  • |–排他锁(X锁,MyISAM 叫做写锁)
  • |–意向共享锁(IS锁,事务打算给数据行加共享锁,但是在这之前必须获得意向共享锁,innodb引擎会自动给符合条件的事务加IS锁)
  • |–意向排他锁(IX锁,事务打算给数据行加排他锁,但是在这之前必须获得意向排他锁,innodb引擎会自动给符合条件的事务加IX锁)
  • |–悲观锁(抽象性,不真实存在这个锁,是一个概念,需要自己编程去实现)
  • |–乐观锁(抽象性,不真实存在这个锁,是一个概念,需要自己编程去实现)
  • |-分布式锁(针对分布式环境下的锁,抽象性,需要通过各种手段实现)

锁类别

不同的应用场景对锁的要求各不相同,我们先来看下锁都有哪些类别,这些锁之间有什么区别。

  • 悲观锁(synchronize)

    • Java 中的重量级锁 synchronize----对代码上锁
    • 数据库行锁-----对数据上锁
  • 乐观锁
    • Java 中的轻量级锁 volatile 和 CAS-----对代码上锁
    • 数据库版本号------对数据上锁
  • 分布式锁
    • 基于数据库实现分布式锁;
    • 基于缓存(Redis等)实现分布式锁;
    • 基于Zookeeper实现分布式锁;

共享锁/排他锁

1)共享锁(Shared Lock)

也称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是都只能读不能修改,因为当多个事务对同一行数据执行修改操作时,会产生死锁现象。

2)排他锁(Exclusive Lock)

也称X锁,获取排他锁的事务可以对数据行读取和修改;如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的共享锁和排他锁,

对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,这里有一个重点就是~~:排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,~~其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在该行上上加其他的锁—(导致不能进行其他的操作)。

共享锁和排他锁是在数据库中实实在在存在的锁!!

Mysql InnoDB引擎默认的修改数据语句:

  • update,delete,insert都会自动给涉及到的数据加上排他锁
  • select语句默认不会加任何锁类型
  • 如果加排他锁可以使用select …for update语句,
  • 加共享锁可以使用select … lock in share mode语句。
  • 所以加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

申请排他锁的前提是,没有线程对该结果集的任何行数据使用排它锁或者共享锁,否则申请会受到阻塞。

乐观锁/悲观锁

注意:乐观锁和悲观锁是抽象性的概念,不是实际存在的一种锁,而是通过实际存在的锁或其他机制来实现乐观锁、悲观锁的思想!!

对于乐观锁和悲观锁的区别及应用,要牢记一句话:读取频繁使用乐观锁,写入频繁使用悲观锁

乐观锁和悲观锁是同一维度的概念,都是从数据访问的角度来说。所以经常出现在数据库相关问题中,即当数据同时被多个对象访问了,应该持什么态度来对数据进行保护。这两种锁,是两种思想。它们的使用是非常广泛的,不局限于某种编程语言或数据库。

1)乐观锁

乐观锁认为,数据被访问,对方不大可能要修改这个数据。所以在此思想的引导下,

  • 一个对象读数据时,不会上锁不会排斥其他的访问(包括读和写)

  • 但是在更新(写)的时候会判断其他线程在这之前有没有对数据进行修改。若有人动过,则取消 重读再重试。

  • 一般会使用版本号机制或CAS操作实现。

2)悲观锁

悲观锁认为,数据被访问,对方很可能要修改这个数据。所以在此思想的引导下,

  • 数据被访问时,不管是读还是写,步步加锁,严格排斥其他对象的访问
  • 可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁

3)乐观锁和悲观锁的实现方式

悲观锁:--------步步加锁

悲观锁的实现方式是加锁:

  • 加锁既可以是对代码块加锁(如Java的synchronized关键字)
  • 也可以是对数据加锁(如MySQL中的排它锁),如行锁、读锁和写锁等。

排他性,只有当前进行加锁的用户,才可以对被锁的数据进行操作,账务中悲观锁的使用相对较多,其他系统使用较少,因为悲观锁影响性能

乐观锁:----------可以理解为并不加锁,只是更新时需要进行判断
  • 数据库版本号方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数

    当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

核心SQL代码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
  • CAS操作方式:即compare and swap,涉及到三个操作数:数据所在的内存值,预期值,新值

    当需要更新时,判断当前内存值与进行比较的预期值是否相等,若相等,则用新值更新;若失败则重试,一般情况下是一个自旋操作,即不断的重试。

CAS包含了Compare和Swap两个操作,它又如何保证原子性呢?

答案是:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。例如在JAVA中,AtomicInteger是java.util.concurrent.atomic包提供的原子类,利用CPU提供的CAS操作来保证原子性;

CAS(Compare and Swap):

CAS是由CPU硬件实现,所以执行相当快。

CAS有三个操作参数:内存值,期望值,要修改的新值

当期望值和内存当中的值进行比较不相等的时候,表示内存中的值已经被别线程改动过,这时候失败返回;当相等的时候,将内存中的值改为新的值,并返回成功。

CAS这种实现方式有什么缺点吗?

1. ABA问题 假设有两个线程——线程1和线程2,两个线程按照顺序进行以下操作:

  • (1)线程1读取内存中数据为A;
  • (2)线程2将该数据修改为B;
  • (3)线程2将该数据修改为A;
  • (4)线程1对数据进行CAS操作

在第(4)步中,由于内存中数据仍然为A,因此CAS操作成功,但实际上该数据已经被线程2修改过了。这就是ABA问题。

在AtomicInteger的例子中,ABA似乎没有什么危害。

但是在某些场景下,ABA却会带来隐患,例如栈顶问题:一个栈的栈顶经过两次(或多次)变化又恢复了原值,但是栈可能已发生了变化。

对于ABA问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都+1;

在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。

Java中的AtomicStampedReference类便是使用版本号来解决ABA问题的。

2.高竞争下的开销问题 在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。

针对这个问题的一个思路是引入退出机制,如重试次数超过一定阈值后失败退出。

当然,更重要的是避免在高竞争环境下使用乐观锁。

3.功能限制 CAS的功能是比较受限的,例如CAS只能保证单个变量(或者说单个内存值)操作的原子性,这意味着:

(1)原子性不一定能保证线程安全,例如在Java中需要与volatile配合来保证线程安全;

(2)当涉及到多个变量(内存值)时,CAS也无能为力。

除此之外,CAS的实现需要硬件层面处理器的支持,在Java中普通用户无法直接使用,只能借助atomic包下的原子类使用,灵活性受到限制。

4)乐观锁和悲观锁优缺点和适用场景

乐观锁和悲观锁并没有优劣之分,它们有各自适合的场景;下面从两个方面进行说明。

  • 功能限制 与悲观锁相比,乐观锁适用的场景受到了更多的限制,无论是CAS还是版本号机制。

    • 比如CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的,而synchronized则可以通过对整个代码块加锁来处理
    • 再比如版本号机制,如果query的时候是针对表1,而update的时候是针对表2,也很难通过简单的版本号来实现乐观锁。
  • 竞争激烈程度 如果悲观锁和乐观锁都可以使用,那么选择就要考虑竞争的激烈程度:

    • 当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源
    • 当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源

分布式锁

  • 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
  • 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。(我觉得分布式情况下之所以问题变得复杂,主要就是需要考虑到网络的延时和不可靠。。。一个大坑)
  • 分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。

1)分布式锁应该具备哪些条件

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:

1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

2)分布式锁的三种实现方式

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要**保证“最终一致性”**,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。分布式锁的三种实现方式有:

  1. 基于数据库实现分布式锁;
  2. 基于缓存(Redis等)实现分布式锁;
  3. 基于Zookeeper实现分布式锁;

3)基于数据库实现分布式锁

具体实现方式有多种,如:

  • 基于表主键唯一做分布式锁
    利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
  • 基于表字段版本号做分布式锁
    这个策略源于 mysql 的 mvcc 机制,使用这个策略其实本身没有什么问题,唯一的问题就是对数据表侵入较大,我们要为每个表设计一个版本号字段,然后写一条判断 sql 每次进行判断,增加了数据库操作的次数,在高并发的要求下,对数据库连接的开销也是无法忍受的。
  • 基于数据库排他锁做分布式锁
    在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁 (注意: InnoDB 引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给要执行的方法字段名添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。

优点:简单,易于理解

缺点:会有各种各样的问题(操作数据库需要一定的开销,使用数据库的行级锁并不一定靠谱,性能不靠谱)

4)基于Redis实现分布式锁

redis加锁的命令setnx,设置锁的过期时间是expire,解锁的命令是del,但是2.6.12之前的版本中,加锁和设置锁过期命令是两个操作,不具备原子性。如果setnx设置完key-value之后,还没有来得及使用expire来设置过期时间,当前线程挂掉了或者线程阻塞,会导致当前线程设置的key一直有效,后续的线程无法正常使用setnx获取锁,导致死锁。针对这个问题,redis2.6.12以上的版本增加了可选的参数,可以在加锁的同时设置key的过期时间,保证了加锁和过期操作原子性的。

5)基于ZooKeeper实现分布式锁:

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

乐观锁、悲观锁、分布式锁的总结相关推荐

  1. 关于分布式锁的面试题都在这里了|Reids分布式锁|ZooKeeper分布式锁

    我今天班儿都没上,就为了赶紧把这篇文章分布式锁早点写完.我真的不能再贴心了. 边喝茶边构思,你们可不要白嫖了! 三连来一遍? 引言 为什么要学习分布式锁? 最简单的理由就是作为一个社招程序员,面试的时 ...

  2. mysql 分布式锁_【分布式锁的演化】分布式锁居然还能用MySQL?

    前言 之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨jvm的场景下,我们又该如何去解决并发. ...

  3. 分布式锁、ZK分布式锁、Redis分布式锁

    常见的分布式锁实现方案:ZK分布式锁.Redis分布式锁 ZK分布式锁: 原理:使用ZK 的临时有序节点.节点的监听机制来实现的. 锁特点:悲观锁,公平锁 获取锁:客户端A在/mylock节点目录下创 ...

  4. 【高并发】高并发分布式锁架构解密,不是所有的锁都是分布式锁!!

    来自:冰河技术 写在前面 最近,很多小伙伴留言说,在学习高并发编程时,不太明白分布式锁是用来解决什么问题的,还有不少小伙伴甚至连分布式锁是什么都不太明白.明明在生产环境上使用了自己开发的分布式锁,为什 ...

  5. redis分布式锁与zk分布式锁的对比

    在分布式环境下,传统的jvm级别的锁会失效,那么分布式锁就是非常有必要的一个技术,一般我们可以通过redis,zk等技术来实现我们的分布式锁 redis实现分布式锁: 原理:我们都知道redis的处理 ...

  6. 【高并发】高并发分布式锁架构解密,不是所有的锁都是分布式锁(升级版)!!

    点击上方蓝色"Garnett的Java之路",关注并选择"设为星标" 持之以恒,贵在坚持,每天进步一点点! 写在前面 最近,很多小伙伴留言说,在学习高并发编程时 ...

  7. 什么是悲观锁和乐观锁,及分布式锁的实现方式

    做程序开发时,都会往并发方面来设计,分布式,集群,负载均衡,读写分离,主从分离,分库分表,缓存等等等手段来提高自己应用服务器的访问负载量  但是也会并发出现另一个问题 当两个线程同时查询到A这个数据  ...

  8. Mysql学习总结(83)——常用的几种分布式锁:ZK分布式锁、Redis分布式锁、数据库分布式锁、基于JDK的分布式锁方案对比总结

    一.基于数据库实现分布式锁 1.1.悲观锁 利用select - where - for update 排他锁.注意: 其他附加功能与实现一基本一致,这里需要注意的是"where name= ...

  9. redis完整笔记总结-数据类型-事务与锁-集群-分布式锁-常见问题(缓存穿透、击穿、雪崩)

    1. 数据类型 五大基本类型 String hash -> 类似map list set -> zset -> 基于set的有序集合 新增 bitmaps:其实就是string,主要 ...

  10. 分布式锁原理——redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

最新文章

  1. IBM AI辩手对战世界级人类辩手,炒作还是秀肌肉?
  2. BorderContainer的圆角问题
  3. React Hook基本使用踩坑指南
  4. 网交会引发的新交易机会
  5. C# 2.0 套接字编程实例初探
  6. 电脑键盘练习_三款神器!超越键盘飞毛腿!
  7. 二叉树层序遍历_求二叉树的层序遍历
  8. JavaScript权威设计--JavaScript表达式与运算符(简要学习笔记五)
  9. ubuntu虚拟机安装Gitlab后出现“Whoops, GitLab is taking too much time to respond.”
  10. NiFi-面向流程的大数据处理框架
  11. Centos7 wordpress4.8.2
  12. Next.js 服务端渲染框架实战
  13. 流体连续性方程【The Equation of Continuity】
  14. 单片机原理与应用实验——定时器(C语言),用定时器设计延时函数,并实现流水灯的功能
  15. 平安产险深圳分公司:绿色保险亮相第十五届深圳国际金融博览会
  16. hadoop - hadoop2.6 伪分布式 示例 wordcount 分词 和 hdfs常用操作命令
  17. 卸载cuda,以及N卡驱动
  18. 分享Canvas简笔画小程序源码
  19. 黑科技手机计算机,轻量又好用的黑科技APP,绝对能让你的手机能量满满!
  20. 函数式编程——python为例

热门文章

  1. CSP-何以包邮?(从背包问题的角度出发)
  2. mysql求2个时间的时间差,mysql求绝对值
  3. Excel行高、列宽怎么调整?
  4. 路由实验总结(思科6.2)
  5. 关系型数据库基础 第一章(共六章节)
  6. Arm发布Cortex-A76AE自动驾驶芯片架构,宣示车载系统市场主权
  7. kali linux arp欺骗钓鱼wifi
  8. 软件模拟SPI时序实现25Q64读写操作
  9. Matcher.matcher()与Matcher.find()的区别
  10. 拥抱元宇宙,“虎视传媒们”靠什么打开想象力?