面试必备之 悲观锁与乐观锁


悲观锁与乐观锁并不是真正意义上的锁,而是对数据的加锁策略

悲观锁(Pessimistic Lock)

是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。

优缺点与适用场景

优点:对每次读取数据都进行加锁,解决了脏读、幻读和不可重复读等可能存在的问题
缺点:每次都加锁降低了系统的吞吐量,并发量大时许多线程都被阻塞
适用场景:适用于读少写多的情况下,经常产生冲突的场景

实现方式(mysql里)

select for .... update
获取锁的前提:结果集中的数据没有使用排他锁或共享锁时,才能获取锁,否则将会阻塞。

例如、

select * from tbl_user where id=1 for update;

需要注意的是,for update 生效需要同时满足两个条件时才生效:

  1. 数据库的引擎为 innoDB
  2. 操作位于事务块中(BEGIN/COMMIT)

当执行 select … for update时,将会把数据锁住,因此,我们需要注意一下锁的级别。MySQL InnoDB 默认为行级锁。当查询语句指定了主键时,MySQL会执行「行级锁」,否则MySQL会执行「表锁」。
常见情况如下:

  • 若明确指明主键,且结果集有数据,行锁;
  • 若明确指明主键,结果集无数据,则无锁;
  • 若无主键,且非主键字段无索引,则表锁;
  • 若使用主键但主键不明确,则使用表锁;

实例
在窗口1上执行

// 关闭mysql数据库的自动提交属性
set autocommit=0;
// 开启事务
BEGIN;
SELECT * FROM tbl_user where id=1 for update;

然后再在窗口2执行获取锁语句 select * from tbl_user where id=1 for update;

那么窗口2并没有像窗口1一样,立刻返回结果,而是发生了阻塞,返回了一个error信息

ERROR 1205: Lock wait timeout exceeded; try restarting transaction

乐观锁(Optimistic Locking)

乐观锁是相对悲观锁而言的,是一种对数据的修改持有悲观态度的并发控制方式。乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。

优缺点与适用场景

优点:省去了锁的开销,加大了系统的整个吞吐量
缺点:1、ABA问题。 2、循环时间长开销大。 3、只能保证一个共享变量的原子操作

  • ABA 问题

线程1从数据库中取出某数据为A,这时候线程2也从数据库中某数据为A;
并且线程2进行了一些操作将数据变成了 B、然后线程2又将数据变成 A,
这时候线程1进行 CAS 操作发现数据库中仍然是A,然后线程一操作成功。
尽管线程一的 CAS 操作成功,但是不代表这个过程就是没有问题的。

  • 循环时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。

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

CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。

适用场景:适用于读多写少的情况(多读场景),冲突较少的场景

实现方式

1、版本号机制

在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加1。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

2、CAS算法
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。

CAS算法涉及到三个操作数
1、需要读写的内存值 V
2、进行比较的值 A
3、拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
悲观锁乐观锁面试中经常出现的问题,它们是并发控制的两种不同的策略。悲观锁的基本思想是,在访问共享资源之前,先将其锁定,以确保在操作期间没有其他线程可以对其进行修改。悲观锁会锁住代码块或数据,其他线程无法同时访问,会影响并发性能。悲观锁适用于竞争激烈的情况,即并发冲突的概率较大。然而,悲观锁的开销较大,加锁和释放锁都需要消耗额外的资源。 乐观锁的基本思想是,假设在大多数情况下,并发冲突是不会发生的,因此不需要加锁,允许多个线程同时访问共享资源。当更新操作需要执行时,乐观锁会比较当前状态与之前读取的状态是否一致,如果一致,则进行更新操作;如果不一致,则表示有其他线程已经修改了该数据,此时需要回滚并重试操作。乐观锁适用于并发冲突较少的情况,这样可以省去锁的开销,提高系统的整体吞吐量。然而,乐观锁在执行更新时可能会频繁失败,需要不断重试,会浪费CPU资源。 在面试中,可能会有一些追问。例如,悲观锁是否会加锁?乐观锁是如何进行回滚和重试的?这些问题都是考查对悲观锁乐观锁的基本原理和实现方式的理解。了解悲观锁乐观锁的优缺点,并能根据具体场景选择适合的并发控制策略,是面试中的关键。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值