什么是悲观锁,乐观锁,举例和适用场景,悲观锁实现

本文介绍了悲观锁和乐观锁的基本概念、数据库中的典型实现以及它们在Java和数据库中的应用。讨论了两者在性能、使用场景和优缺点方面的区别,强调了选择哪种锁取决于具体的应用需求。
摘要由CSDN通过智能技术生成

一、悲观锁和乐观锁基本概念和由来

整体来说悲观锁和乐观锁是一种变成的思想,并不是实际的有这么一种类或者锁存在。
悲观锁和乐观锁最早是用于数据库设计,后续被引入Java 中,也逐渐扩散到其他领域。

悲观锁:
即认为每一次数据操作都有可能发生并发问题,不加锁就一定会出问题,做操作之前直接将数据锁定,在本次操作结束之前其他人无法完成操作。
例如:Java中 SynchronizedReentrantLock(Lock的实现类) 等独占锁就是悲观锁思想的实现。

乐观锁:
与乐观锁对立,认为每一次数据操作都不会有并发问题,只有在更新数据的时候会检查当前操作的数据是否还是和原有保持一致,一致则执行自己的更新,如果原有数据被修改则不执行数据更新。
例如Lock 是乐观锁的典型实现案例。

悲观锁举例:

SynchronizedLock的实现类 ReentrantLock 都是悲观,这里的以 ReentrantLock 为典例,该类中使用 lock() 就是加锁,unlock() 就是解锁。
处理资源前一定加锁并尝试获取锁,执行结束再释放。

乐观锁:

原子类是乐观锁的典型案例,例如 AtomicInteger 在更新数据时,就使用了乐观锁的思想,多个线程可以同时操作同一个原子变量。

二、数据库中悲观锁乐观锁的典型实现

数据库中的悲观锁
例如语句:SELECT * FROM 某张表 WHERE 某些条件 FOR UPDATE;
(这里和大小写无关,只是个人习惯 (lll¬ω¬))

这里的 SELECT … FOR UPDATE 就是悲观锁的一种实现,意义为会锁定本次查询的数据,在本次事务提交之前不允许其他人来修改,这就比较霸道了,所以对性能的损耗是很大的,用的不恰当可能会引起长时间的等待。

数据库中的乐观锁
典型的在数据库表中增加一个标识字段,通常为 version 直译为版本 的字段,当然这个标识的名字可以随意更改,看自己的场景,但意义是一样的,每次成功改动后该字段发生变化或者自动加1。

乐观锁的情况下当我们查询和修改数据时都不会去做限制,只有当我们需要提交本次修改的时候需要检查当前的记录的这个标识字段是否和我们刚拿到时一样,例如刚开始拿到本条数据的时候 version 为 1,准备提交修改的时候也为1,那么就没有人动过,我们就可以执行更新操作,并将该字段加 1。

如果准备修改的时候发现该字段 version 变成 2 了,与原有不一致那么就不能提交,可以重新基于现有数据再次计算后,再次执行以上流程,直到执行成功。

Java 中乐观锁 CAS (CompareAndSwap)操作:
CAS (CompareAndSwap 比较并交换)
CAS是一个多线程同步的原子指令,CAS操作包含三个重要的信息,即内存位置预期原值新值。如果内存位置的值和预期的原值相等,那么把该位置的值更新为新值,否则不做修改。
补充说明: 虽然ReentrantLock也是通过CAS实现的,但是是悲观锁

但 CAS 可能造成 ABA 问题,ABA 问题即 中间的内容可能发生了变化,类似与我买了一个行李箱,然后向里面放了很多东西,但有人趁我不注意把里面的东西换了或者拿走了,但当我需要使用的时候看到行李箱和原来是一样的,那么就还是认为没有什么问题。

JDK 1.5 提供了AtomicStampedReference 类也可以解决ABA的问题
此类维护了一个“版本号”Stamp,每次在比较时不止比较当前值还比较版本号,这样就解决了 ABA 的问题。

三、使用场景和优缺点

悲观锁大多数基于数据库本身的机制来实现,但对于时间比较久的操作来说,对于整体的耗损和性能会比较差,而乐观锁的开销相比之下就要小很多,即使是并发场景性能也比较不错。

乐观锁的实现大多依赖于我们自己的业务系统内部实现,当有第三方接入也来操作时可能会出现错误数据,此时对于数据库就不该直接开放给其他人,而是通过我们设计的过程来完成对数据库的操作。

悲观锁的性能一定不如乐观锁吗? 显然不是,要按照场景来尝试和区分。
悲观锁是重量级的,也不能多线程并行执行,甚至还有上下文切换,所以开销自然要比乐观锁开销大一点。
但悲观锁的开销是固定的,即使再多的请求也一样,但乐观锁不同,由于乐观锁的反复重试的机制,当竞争非常激烈的情况下,会消耗大量算力和资源,最终整体的性能和速度反而会降低。

悲观锁场景
并发写入多,竞争十分激烈,临界区代码复杂,如果使用乐观锁可能造成大量无用的算力消耗,那么可以使用悲观锁来完成。

乐观锁场景:
并发读多,修改少的场景。在竞争不太激烈的情况下读多写多也可以考虑使用。

参考文献:https://juejin.cn/post/7087436837911789576

四、悲观锁实现

  1. 代码 SQL 手动开启事务 begin; begin work; start transaction;
  2. SELECT … FOR UPDATE; 完成加锁
  3. 完成操作后 通过 commit; 完成事务释放锁
  4. innodb 引擎默认行级锁
  5. 查不到数据时不锁

通过 WHERE 条件的不同,锁定的方式也不同,具体再需要学习数据库的行锁和表锁的机制等

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值