首页 > 其他分享 >乐观锁与悲观锁

乐观锁与悲观锁

时间:2023-05-10 21:23:07浏览次数:48  
标签:CAS 更新 乐观 版本号 version 线程 悲观 操作

1、乐观锁

乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。
高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。但是,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。
乐观锁通常多于写比较少的情况下(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考java.util.concurrent.atomic包下面的原子变量类)。

1.1 版本号机制

一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
举一个简单的例子 :假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
操作员 A 此时将其读出( version=1),并从其帐户余额中扣除 $50( $100-$50 )。
在操作员 A 操作的过程中,操作员 B也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
操作员 A 完成了修改工作,将数据版本号( version=1 ),连同帐户扣除后余额( balance=$50),提交至数据库更新,此时由于提交数据版本等于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
操作员 B 完成了操作,也将版本号( version=1)试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 1 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须等于当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样就避免了操作员 B 用基于 version=1的旧数据修改的结果覆盖操作员 A 的操作结果的可能。

1.2 CAS算法

CAS 的全称是 Compare And Swap(比较与交换),用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
CAS 是一个原子操作,底层依赖于一条CPU 的原子指令。
原子操作 即最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。
CAS 涉及到三个操作数:
V :要更新的变量值(Var)
E :预期值(Expected)(感觉跟版本号一样)
N :拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。
举一个简单的例子 :线程 A要修改变量 i 的值为 6,i 原值为 1(V = 1,E=1,N=6,假设不存在 ABA 问题)。
i 与 1 进行比较,如果相等,则说明没被其他线程修改,可以被设置为 6 。
i 与 1 进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更新,CAS 操作失败。
当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
Java 语言并没有直接实现 CAS,CAS 相关的实现是通过 C++ 内联汇编的形式实现的(JNI 调用)。因此, CAS 的具体实现和操作系统以及 CPU 都有关系。

1.3 乐观锁存在的问题

  • ABA问题
    如果一个变量 V 初次读取的时候是 A值,并且在准备赋值的时候检查到它仍然是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 "ABA"问题。ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。
  • 循环时间长开销大
    CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功。如果长时间不成功,会给 CPU 带来非常大的执行开销。
    如果 JVM 能支持处理器提供的pause 指令那么效率会有一定的提升,pause 指令有两个作用:
    1、可以延迟流水线执行指令,使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。
    2、可以避免在退出循环的时候因内存顺序冲而引起 CPU 流水线被清空,从而提高 CPU 的执行效率。
  • 只能保证一个共享变量的原子操作
    CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5 开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

2、悲观锁

悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题,影响代码的正常运行。
悲观锁通常多用于写比较多的情况下(多写场景,竞争激烈),这样可以避免频繁失败和重试影响性能,悲观锁的开销是固定的。

参考

https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#乐观锁和悲观锁

标签:CAS,更新,乐观,版本号,version,线程,悲观,操作
From: https://www.cnblogs.com/roadwide/p/17389370.html

相关文章

  • MySQL(二十)锁(三)乐观锁与悲观锁、显示隐式锁和其他锁
    MySQL(二十)锁(三)乐观锁与悲观锁显式锁和隐式锁1从对待锁的态度划分:乐观锁、悲观锁从对待锁的态度划分,可以将锁划分为乐观锁和悲观锁,可以看出这两种锁是两种对待数据并发的思维方式。乐观锁和悲观锁并不是锁,而是锁的设计思想1.1乐观锁乐观锁对数据被其他事务修改持有乐观......
  • MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
    锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具。在计算机中,是协调多个进程或县城并发访问某一资源的一种机制。在数据库当中,除了传统的计算资源(CPU、RAM、I/O等等)的争用之外,数据也是一种供许多用户共享访问的资源。如何保证数据并发访问的一致性、有效性,是所有数据库必须......
  • Django进阶:事务操作、悲观锁和乐观锁
    Django进阶:事务操作、悲观锁和乐观锁参考网址https://zhuanlan.zhihu.com/p/372957129事务处理(transaction)对于Web应用开发至关重要,它可以维护数据库的完整性,使整个系统更加安全。比如用户A通过网络转账给用户B,数据库里A账户中的钱已经扣掉,而B账户在接收过程中服务器......
  • 关于乐观锁、悲观锁、可重入锁....
    并发编程----乐观锁、悲观锁、可重入锁…..作为一个Java开发多年的人来说,肯定多多少少熟悉一些锁,或者听过一些锁。今天就来做一个锁相关总结。需要高清图,进入公众号联系我悲观锁和乐观锁悲观锁顾名思义,他就是很悲观,把事情都想的最坏,是指该锁只能被一个线程锁持有,如果A线程获......
  • 面试必备之乐观锁与悲观锁 一般有用 看1
    何谓悲观锁与乐观锁乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿......
  • hibernate 乐观锁与悲观锁使用
    评:Hibernate支持两种锁机制:即通常所说的“悲观锁(PessimisticLocking)”和“乐观锁(OptimisticLocking)”。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。......
  • 乐观锁和悲观锁
    什么是悲观锁?悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。像Java中synchronized和ReentrantLock等独占锁就是悲观锁思......
  • python-悲观锁和乐观锁
    乐观锁和悲观锁它们都是一种思想,都是人们定义出来的概念,和语言无关并发控制:当程序出现并发的问题时,我们需要保证在并发情况下数据的准确性,以保证当前用户在和其他用户一起操作时,得到的结果和他单独操作时得到的结果是一样的,没有做好并发控制,就可能导致脏读、幻读、不可重复读等问......
  • 乐观锁,悲观锁
    悲观锁乐观锁的实现fromdjango.shortcutsimportrender,HttpResponse#Createyourviewshere.from.modelsimportBook,Orderfromdjango.dbimporttransaction###回滚点的使用#defseckill(request):#withtransaction.atomic():##设置回滚......
  • mybatisPlus-乐观锁
    数据库中添加version字段  自定义配置类中,添加乐观锁的拦截器packagecom.atguigu.config;importcom.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;importcom.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;im......