乐观锁与悲观锁

乐观锁和悲观锁都是用于解决并发场景下的数据竞争问题,但是却是两种完全不同的思想。它们的使用非常广泛,也不局限于某种编程语言或数据库

乐观锁 

认为读多写少,遇到并发写的可能性低,每次拿数据的时候认为别人不会修改,所以默认不会上锁, 但是在执行更新的时候才会去判断在此期间是否被修改了数据, 如果被修改了, 则放弃操作 .

冲突比较少的场景时候, 使用乐观锁(没有悲观锁那样耗时的开销) 由于乐观锁的不上锁特性,所以在性能方面要比悲观锁好,比较适合用在DB的读大于写的业务场景。

悲观锁

:认为读少写多,遇到并发写的可能性高,每次拿数据的时候认为别人会修改,所以默认会上锁 

冲突比较多的时候, 使用悲观锁(没有乐观锁那么多次的尝试)对于每一次数据修改都要上锁,如果在DB读取需要比较大的情况下有线程在执行数据修改操作会导致读操作全部被挂载起来,等修改线程释放了锁才能读到数据,体验极差。所以比较适合用在DB写大于读的情况。 

读取频繁使用乐观锁,写入频繁使用悲观锁。 

乐观锁的实现方式

  乐观锁的实现方式主要有两种,一种是CAS(Compare and Swap,比较并交换)机制,一种是版本号机制。 

CAS机

CAS操作包括了三个操作数,分别是需要读取的内存位置(V)、进行比较的预期值(A)和拟写入的新值(B),操作逻辑是,如果内存位置V的值等于预期值A,则将该位置更新为新值B,否则不进行操作。另外,许多CAS操作都是自旋的,意思就是,如果操作不成功,就会一直重试,直到操作成功为止。

 在并发冲突的概率较大的高竞争场景下,如果CAS操作一直失败,就会一直重试,造成CPU开销大的问题。针对这个问题,一个简单的思路是引入退出机制,如果重试次数超过一定阈值,就强制失败退出。当然了,最好是避免在高竞争的场景下使用乐观锁。

版本号机制

版本号机制的基本思路,是在数据中增加一个version字段用来表示该数据的版本号,每当数据被修改版本号就会加1。当某个线程查询数据的时候,会将该数据的版本号一起读取出来,之后在该线程需要更新该数据的时候,就将之前读取的版本号与当前版本号进行比较,如果一致,则执行操作,如果不一致,则放弃操作。

功能限制

乐观锁与悲观锁相比,适用的场景受到了更多的限制,无论是CAS机制还是版本号机制.

比如,CAS机制只能保证单个变量操作的原子性,当涉及到多个变量的时候,CAS机制是无能为力的,而synchronized却可以通过对整个代码块进行加锁处理;再比如,版本号机制如果在查询数据的时候是针对表1,而更新数据的时候是针对表2,也很难通过简单的版本号来实现乐观锁。

ABA 问题是乐观锁一个常见的问题

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

悲观锁的实现方式

悲观锁的实现方式也就是加锁,加锁既可以在代码层面(比如Java中的synchronized关键字),也可以在数据库层面(比如MySQL中的排他锁)。 

总结

乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机智的其实都是提供的乐观锁。 相反,如果经常发生冲突,上层应用会不断进行 retry,这样反而降低了性能,所以这种情况下用悲观锁比较合适

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值