乐观锁和悲观锁的理解及如何实现,乐观锁的实现方式。看完你就明白了!

文章介绍了锁在并发控制中的作用,详细对比了悲观锁和乐观锁的概念。悲观锁在访问数据时预设会有冲突,因此采用加锁机制;而乐观锁则假设很少有冲突,在更新时才检查是否有变化。文中提到了乐观锁的CAS算法及其ABA问题,并指出两者在不同场景下的适用性。
摘要由CSDN通过智能技术生成

前言

  • 在了解悲观锁和乐观锁之前,我们先了解一下什么是锁,为什么要用到锁?

  • 技术来源于生活,锁不仅在程序中存在,在现实中我们也随处可见,例如我们上下班打卡的指纹锁,保险柜上的密码锁,以及我们我们登录的用户名和密码也是一种锁,生活中用到锁可以保护我们人身安全(指纹锁)、财产安全(保险柜密码锁)、信息安全(用户名密码锁),让我们更放心的去使用和生活,因为有锁,我们不用去担心个人的财产和信息泄露。

  • 而程序中的锁,则是用来保证我们数据安全的机制和手段,例如当我们有多个线程去访问修改共享变量的时候,我们可以给修改操作加锁(syncronized)。当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁)。因此,当程序中可能出现并发的情况时,我们就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的

  • 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题,如下图所示:
    在这里插入图片描述
    由于并发操作,如果没有加锁进行并发控制,数据库的最终的一条数据可能为3也有可能为5,导致数值不准确

面试回答话术:

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候
都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做
操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是
在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机
制,其实都是提供的乐观锁。
在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实
现的。

乐观锁的实现方式:

  1. 使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可 以采取丢弃和再次尝试的策略。
  2. java中的Compare and Swap即CAS ,当多个线程尝试使用CAS同时更新同一个变量时,只有其
    中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争 中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。

CAS缺点:

  1. ABA问题:比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出
    A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行
    CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在
    潜藏的问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决 ABA问题。

  2. 循环时间长开销大:对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,
    从而浪费更多的CPU资源,效率低于synchronized。

  3. 只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的
    方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个 时候就可以用锁。

悲观锁和乐观锁

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

2.1、悲观锁

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

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

数据库中的行锁,表锁,读锁(共享锁),写锁(排他锁),以及syncronized实现的锁均为悲观锁
在这里插入图片描述
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证,
在这里插入图片描述

2.2、乐观锁

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

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本(version)或者是时间戳来实现,不过使用版本记录是最常用的。
在这里插入图片描述
乐观控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖天才小朱(怀玉)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值