2019-12-26 悲观锁与乐观锁
2019-12-26 本文已影响0人
Yangxy_Lazy
一、悲观锁
- 悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能变动(考虑到最坏情况),一个事务拿到悲观锁后(可以理解为一个用户),其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。
- 体现:数据库中的行锁,表锁,读锁,写锁,Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现
二、乐观锁
-
乐观锁(Optimistic Concurrency Control)的“乐观情绪”体现在,它认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。
-
在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以
使用版本号机制和 CAS 算法实现 -
体现:像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了 乐观锁的一种实现方式 CAS 实现的
-
版本号机制:乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。
- 事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比, 如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。 如果,v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。
-
CAS(compare and swap)算法:无锁算法。无锁编程, 即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的 情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking
Synchronization)
- CAS 算法涉及到三个操作数
- 需要读取内存值V
- 进行比较的值 A
- 拟写入的新值B
- 当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则 不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操
作,即不断的重试
三、总结
-
悲观锁和乐观锁是用来控制并发下数据的顺序变动问题的
-
悲观锁(应用在写为居多的场景,多写场景,冲突一般较多)
- 优点:悲观锁利用数据库中的锁机制来实现数据变化的顺序执行,这是最有效的办法 - 缺点:一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,那势必影响我们系统的吞吐量。 - 场景:多写的场景下用悲观锁就比较合适
-
乐观锁(应用在读为居多的场景,多读场景,冲突一般较少)
- 优点:乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。 - 缺点:乐观锁因为是通过我们人为实现的,它仅仅适用于我们自己业务中,如果有外来事务插入( "ABA"问题,中途被其他事务插入,并修改,再改回去),那么就可能发生错误。 - 场景:乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的 时候,这样可以省去了锁的开销,加大了系统的整个吞吐量
四、补充
- synchronized 的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继 续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况 下,可以获得和 CAS 类似的性能;而线程冲突严重的情况下,性能远高于CAS