当前位置:首页 > 天道酬勤 > 正文内容

悲观锁怎么实现(悲观锁与乐观锁具有更好的并发性能)

张世龙2021年12月20日 04:25天道酬勤1280

推荐学习

牛掰! “基础-中级-高级”Java程序员面试集结,看完之后膝盖用力! 耗尽了这个“核心知识”“高级面试”,通过了腾讯javat4

前言

只有孩子选择了,我都想要。 今天我要试着写下面试中一定要听的内容:乐观摇滚和悲观摇滚。 主要从以下几点出发:

乐观锁是什么悲观锁乐观锁常用实现方式悲观锁常用实现方式乐观锁的缺点悲观锁的缺点悲观锁的缺点在写文章时,突然收到朋友的信息,乌兹退役,LPL0006号选手说已经切断。 就像你穿着新鲜衣服惹马生气,整天看长安花,翻山越河,过万里,回来还是那个少年一样。 来吧,和我一起喊“从大街到简——只有我感到自豪”

1、何为乐观锁

乐观锁定假设事情总是朝着好的方向发展。 例如,有些人天生乐观,天生就向阳

乐观锁定总是假定最好的情况,每次去取数据都认为别人不修改而不上锁,但在更新期间判断别人是否去更新了该数据。 乐观锁定适用于多种读取APP。 由于乐观锁定不会在读取数据时解锁,因此消除了锁定开销,从而提高了整个系统的吞吐量。 即使偶尔发生冲突,这也是无害的。 请重试,或者返回用户,说新的失败。 当然,前提是偶尔发生碰撞,但如果频繁发生碰撞,上层APP将不断进行旋转重试,反而会降低性能,造成损失。

2、何为悲观锁

悲观摇滚假设事情正朝着不好的方向发展。 例如,如果一个人经历了什么,他可能不太相信别人,只信任自己,置身于黑暗之中,践踏光明。

悲观的锁觉得每次去取数据别人都会修改,所以每次取数据都会上锁。 这样的话,别人试图获取这个数据会被阻止。 在我解锁之前,其他人拿不到锁。 这样可以确保数据的准确性,因为数据只由自己的线程进行了修改。 因此,悲观锁定适用于进行多次写入的APP类型。

3、乐观锁常用实现方式

3.1 版本号机制

版本号的工作原理是在表中添加字段。 version在修改记录时,首先查询记录,每次修改时将此字段的值加1。 判断条件是刚才查询的值。 看看下面的流程就知道了。

3.1.1用户信息表create表`用户信息` (

` ID`bigint(20 )非空自动_增量注释'主键id '、

`用户名` varchar (64 )默认空值注释)用户名)、

剩余的金额)部分)、

版本(20默认1版本号)、

主密钥使用树

) ENGINE=InnoDB COMMENT='用户信息表'; 3.1.2数据插入信息,1000,1 ); 3.1.3操作步骤 )。

3.2 CAS算法

CAS是比较和交换(比较和交换),是有名的无锁定算法。 由于无需锁定编程,即无需锁定即可实现多线程间的变量同步,因此也称为非阻塞同步(Non-blocking Synchronization )。 CAS算法包括三个操作数:

需要读写的内存值v (主存储器中的变量值)比较的值a )克隆的线程局部存储器中的变量值)写入的新值b )更新的新值)仅在v的值等于a时,CAS用新值b原子更新v的值。 否则什么也不做(比较和替换是自然原子操作。 通常,这是旋转操作。 也就是说,重复重试。 请看下面的过程。

3.2.1 CAS算法是数据库的更新数据(表仍是刚才的表,用户jzdxhd的金额初始值为1000 ),用户jzdxhd的金额为100 (私有视点数据)。

//死循环

for (; () )

获取jzdxhd的金额

bigdecimal money=this .用户映射器. get money by name;

用户用户

= new User(); user.setMoney(money); user.setUserName(userName); // 根据用户名和金额进行更新(金额+100) Integer updateCount = this.userMapper.updateMoneyByNameAndMoney(user); if (updateCount != null && updateCount.equals(1)){ // 如果更新成功就跳出循环 break; } } }3.2.2 流程图如下:

看到这里,明眼人都发现了一些CAS更新的小问题,至于是什么问题呢、怎么解决呢,放在下面来讲,要不然下面几条就没得写了。。。。。。

注意,这里的加版本号机制和CAS出现ABA问题加版本号解决机制不是同一个。

4、悲观锁常用实现方式

4.1 ReentrantLock

可重入锁就是悲观锁的一种,如果你看过前两篇文章,对可重入锁的原理就很清楚了,不清楚的话就看下如下的流程:

假设同步状态值为0表示未加锁,为1加锁成功

可以看到,只要线程A获取了锁,还没释放的话,线程B是无法获取锁的,除非A释放了锁,B才能获取到锁,加锁的方式都是通过CAS去比较再交换,B会尝试自旋去设置,在尝试几次之后,就会阻塞线程,等到前驱节点出队通知之后再次尝试获取锁,这也就说明了为啥悲观锁比起乐观锁来说更加消耗性能。

4.2 synchronized

其实和上面差不多的,只不过上面自身维护了一个volatile int类型的变量,用来描述获取锁与释放锁,而synchronized是靠指令判断加锁与释放锁的,如下代码:

public class synchronizedTest { 。。。。。。 public void synchronizedTest(){ synchronized (this){ mapper.updateMoneyByName("jzdxhd"); } } }

上面代码对应的流程图如下:

如果在某个线程执行synchronizedTest()方法的过程中出现了异常,monitorexit指令会插入在异常处,ReentrantLock需要你手动去加锁与释放锁,而synchronized是JVM来帮你加锁和释放锁。

5、乐观锁的缺点

5.1.1 ABA 问题

上面再说乐观锁用CAS方式实现的时候有个问题,明眼人能发现的,不知道各位有没有发现,问题如下:

解决方案:

给表增加一个version字段,每修改一次值加1,这样就能在写入的时候判断获取到的值有没有被修改过,流程图如下:

5.1.2 循环时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。个人想法是在死循环添加尝试次数,达到尝试次数还没成功的话就返回失败。不确定有没有什么问题,欢迎指出。

5.1.3 只能保证一个共享变量的原子操作

CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

6、悲观锁的缺点

6.1 synchronized

锁的释放情况少,只在程序正常执行完成和抛出异常时释放锁;试图获得锁是不能设置超时;不能中断一个正在试图获得锁的线程;无法知道是否成功获取到锁;

6.2 ReentrantLock

需要使用import 引入相关的Class;不能忘记在finally 模块释放锁,这个看起来比synchronized 丑陋;synchronized可以放在方法的定义里面, 而reentrantlock只能放在块里面. 比较起来, synchronized可以减少嵌套;

作者:少年起而行之原文链接:https://juejin.im/post/5ed73b8251882543413c1240

扫描二维码推送至手机访问。

版权声明:本文由花开半夏のブログ发布,如需转载请注明出处。

本文链接:https://www.zhangshilong.cn/work/25379.html

分享给朋友:

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。