悲观锁与乐观锁的实现(详情图解)

悲观锁与乐观锁的实现(详情图解)一 前言在了解悲观锁和乐观锁之前 我们先了解一下什么是锁 为什么要用到锁 技术来源于生活 锁不仅在程序中存在 在现实中我们也随处可见 例如我们上下班打卡的指纹锁 保险柜上的密码锁 以及我们我们登录的用户名和密码也是一种锁 生活中用到锁可以保护我们人身安全 指纹锁 财产安全 保险柜密码锁 信息安全 用户名密码锁 让我们更放心的去使用和生活 因为有锁 我们不用去担心个人的财产和信息泄露 而程序中的锁 则是用来保证我们数据安全的机制和手段 例如当我们有多个线程去访问修改共享变量的时候 我们

一、前言

  • 在了解悲观锁和乐观锁之前,我们先了解一下什么是锁,为什么要用到锁?
  • 技术来源于生活,锁不仅在程序中存在,在现实中我们也随处可见,例如我们上下班打卡的指纹锁,保险柜上的密码锁,以及我们我们登录的用户名和密码也是一种锁,生活中用到锁可以保护我们人身安全(指纹锁)、财产安全(保险柜密码锁)、信息安全(用户名密码锁),让我们更放心的去使用和生活,因为有锁,我们不用去担心个人的财产和信息泄露。
  • 而程序中的锁,则是用来保证我们数据安全的机制和手段,例如当我们有多个线程去访问修改共享变量的时候,我们可以给修改操作加锁(syncronized)。当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁)。因此,当程序中可能出现并发的情况时,我们就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的
  • 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题,如下图所示:
    在这里插入图片描述
    由于并发操作,如果没有加锁进行并发控制,数据库的最终的一条数据可能为3也有可能为5,导致数值不准确

二、悲观锁和乐观锁

首先我们需要清楚的一点就是无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。

2.1、悲观锁

悲观锁(Pessimistic Lock): 就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程

但是在效率方面,处理加锁的机制会产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,如果已经锁定了一个线程A,其他线程就必须等待该线程A处理完才可以处理

数据库中的行锁,表锁,读锁(共享锁),写锁(排他锁),以及syncronized实现的锁均为悲观锁

在这里插入图片描述
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证,
在这里插入图片描述

2.2、乐观锁

乐观锁(Optimistic Lock): 就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作),乐观锁适用于多读的应用类型,这样可以提高吞吐量

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本(version)或者是时间戳来实现,不过使用版本记录是最常用的。

在这里插入图片描述
乐观控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

三、锁的实现

悲观锁阻塞事务、乐观锁回滚重试:它们各有优缺点,不要认为一种一定好于另一种。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

3.1 悲观锁的实现方式

场景:

有用户A和用户B,在同一家店铺去购买同一个商品,但是商品的可购买数量只有一个

悲观锁的实现,往往依靠数据库提供的锁机制,在数据库中,我们如何用悲观锁去解决这个事情呢?

  1. 加入当用户A对下单购买商品(臭豆腐)的时候,先去尝试对该数据(臭豆腐)加上悲观锁
  2. 加锁失败:说明商品(臭豆腐)正在被其他事务进行修改,当前查询需要等待或者抛出异常,具体返回的方式需要由开发者根据具体情况去定义
  3. 加锁成功:对商品(臭豆腐)进行修改,也就是只有用户A能买,用户B想买(臭豆腐)就必须一直等待。当用户A买好后,用户B再想去买(臭豆腐)的时候会发现数量已经为0,那么B看到后就会放弃购买
  4. 在此期间如果有其他对该数据(臭豆腐)做修改或加锁的操作,都会等待我们解锁后或者直接抛出异常

在这里插入图片描述

那么如何加上悲观锁呢?我们可以通过以下语句给id=2的这行数据加上悲观锁,首先关闭MySQL数据库的自动提交属性。因为MySQL默认使用autocommit模式,也就是说,当我们执行一个更新操作后,MySQL会立刻将结果进行提交,(sql语句:set autocommit=0)

悲观锁加锁sql语句: select num from t_goods where id = 2 for update

我们通过开启mysql的两个会话,也就是两个命令行来演示:

事务A:
我们可以看到数据是立刻马上就可以查询出来,num=1
在这里插入图片描述
事务B:
我们是可以看到,事务B会一直等待事务A释放锁。如果事务A长期不释放锁,那么最终事务B将会报错,报错如下:Lock wait timeout exceeded; try restarting transaction,表示语句已被锁住
在这里插入图片描述
现在我们让事务A执行命令去修改数据,让臭豆腐的数量减一,然后查看修改后的数据,最后commit,结束事务





在这里插入图片描述

3.1 乐观锁的实现方式

在这里插入图片描述

1、首先我们开启两个会话窗口,输入查询语句:select num from t_goods where id = 2
事务A:
在这里插入图片描述

事务B:
在这里插入图片描述

这个时候事务A和事务B同时获取相同的数据

3.1.1 CAS

ABA 问题:
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 “ABA”问题。

ABA 问题解决:
我们需要加上一个版本号(Version),在每次提交的时候将版本号+1操作,那么下个线程去提交修改的时候,会带上版本号去判断,如果版本修改了,那么线程重试或者提示错误信息~

四、如何选择

悲观锁阻塞事务,乐观锁回滚重试,它们各有优缺点,不要认为一种一定好于另一种。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。

但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

注意点:

1、乐观锁并未真正加锁,所以效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。

2、悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

五、总结

这篇文章讲解了悲观锁与乐观锁的区别,以及实现场景,不管是悲观锁还是乐观锁都是人们定义出来的概念,是一种思想,如何有有疑问或者问题的小伙伴可以在下面进行留言,小农看到了会第一时间回复大家,谢谢,大家加油~

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209478.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午9:08
下一篇 2026年3月19日 上午9:09


相关推荐

  • OpenClaw安全防护策略部署:企业级AI智能体安全加固与合规落地指南

    OpenClaw安全防护策略部署:企业级AI智能体安全加固与合规落地指南

    2026年3月12日
    2
  • 看上市公司公告的app_毒APP公告

    看上市公司公告的app_毒APP公告用户为本,让用户成为CSDN产品的主人,为此,我们特开设了CSDN产品公告栏,切实听取大家对新功能的反馈,定期抽取部分反馈用户赠送精美礼品一份!在过去一周,CSDN研发团队又上线了哪些功能呢?让我一起看下:CSDNAPP发布最新版,新增大厂在线刷题功能CSDN博主排名更新,原创优质博文更容易得到曝光MD编辑器优化操作更便捷更加极客酷炫的博客皮肤3.0上线绑定脉脉即可获得专属勋…

    2022年8月13日
    9
  • R语言画图——添加数学表达式和R2[通俗易懂]

    R语言画图——添加数学表达式和R2代码如下:filepath<-file.choose()df1<-read.csv(filepath,header=T)df1library(ggplot2)QTs<-ggplot(data=df1,aes(x=Ts,y=Q10,shape=factor))+geom_point(size=3)+scale_shape_manual(values=c(1,17))+#白天

    2022年4月17日
    39
  • 最全!最完整的递归下降分析法代码!!! (实验报告,代码)

    最全!最完整的递归下降分析法代码!!! (实验报告,代码)根据某一文法编制调试递归下降分析程序 以便对任意输入的符号串进行分析 本次实验的目的主要是加深对递归下降分析法的理解 程序要求 程序输入 输出示例 对下列文法 用递归下降分析法对任意输入的符号串进行分析 1 E gt TG 2 G gt TG TG 3 G gt 4 T gt FS 5 S gt FS FS 6 S gt 7 F gt E 8 F gt i 输出的格式如下 1 递归下降分析程序 编制人 姓名 学号 班级 2 输入一以 结束的符号

    2026年3月17日
    2
  • c语言删除数组中的元素「建议收藏」

    c语言删除数组中的元素「建议收藏」删除一个元素,相同也可删除核心思想:1.找到元素用if语句2.删除就是用后面的代替该元素(需要删除的元素),用for语句3.遍历(就是用for循环看一遍数列)就可以找到想要删除的元素,4.注意最后要给末尾换成零,因为后面的是随机的不一定为零#include<stdio.h>intmain(){ inti,a[10]; intb,c; //输入数组值 printf(“输入数组的值”); for(i=0;i<10;i++) { scanf(“%d”

    2022年7月22日
    26
  • 更换CSDN博客皮肤[通俗易懂]

    更换CSDN博客皮肤[通俗易懂]1.在博客设置页面F12,如下图,选中博客皮肤:2.把你喜欢的皮肤的value和ID与当前模板value和ID对换,如下图:3.点击保存之后刷新页面,如下图:…

    2022年7月14日
    22

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号