乐观锁与悲观锁

1、悲观锁

1.1 定义

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。在Java中,synchronized从偏向锁、轻量级锁到重量级锁,全是悲观锁。JDK提供的Lock实现类全是悲观锁

手动加悲观锁:

  • 读锁:LOCK tables test_db read,释放锁:UNLOCK TABLES;
  • 写锁:LOCK tables test_db WRITE,释放锁:UNLOCK TABLES;

读锁与写锁

  • 如果要更新数据,那么加锁的时候就直接加写锁,一个线程持有写锁的时候别的线程无论读还是写都需要等待;
  • 如果是读取数据仅为了前端展示,那么加锁时就明确地加一个读锁,其他线程如果也要加读锁,不需要等待,可以直接获取(读锁计数器+1)
  • 虽然读写锁感觉与乐观锁有点像,但是读写锁是悲观锁策略。因为读写锁并没有在更新前判断值有没有被修改过,而是在加锁前决定应该用读锁还是写锁。

1.2 特点

  • 优点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;
  • 缺点:因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;

2、乐观锁

2.1 定义

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下,在此期间有没有别人去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS。

形象化记忆:乐观锁认为对数据的操作不会产生冲突,所以不会加锁,而是在提交更新的时候才会对数据的冲突与否进行检测。如果发现冲突,要么再重试一次,要么切换为悲观的策略。乐观并发控制要解决的是数据库并发场景下的写-写冲突,指用无锁的方式去解决。

2.2 特点

  • 优点:乐观锁是一种并发类型的锁,其本身并不对数据进行加锁,而是通循环重试CAS进而实现锁的功能其不对数据进行加锁就意味着允许多个线程同时读取(因为根本没有加锁操作)数据,但是只有一个线程可以成功更新数据,并导致其他要更新数据的线程回滚重试,这种方式大大的提高了数据操作的性能因为整个过程中并没有“加锁”和“解锁”操作,因此乐观锁策略也被称为无锁编程。

  • 缺点

2.3 乐观锁常见的两种实现方式

乐观锁一般会使用版本号机制或CAS算法实现

2.3.1 版本号机制

在这里插入图片描述

2.3.2 CAS算法
2.3.2.1 CAS算法的定义

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

  • 需要读写的内存值 V
  • 旧的预期值 A
  • 拟写入的新值 B

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

2.3.2.2 举例
  • t1和t2线程同时去修改内存中的同一变量56,他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。
  • 假设t1在与t2线程竞争中,线程t1能去更新变量的值,而其他线程都失败(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。
  • t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。
  • 通俗的解释是:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升
2.3.2.3 CAS算法的缺点
  • ABA 问题及其 解决方案 :

thread1意图对val=1进行操作变成2,cas(val,1,2)。
thread1先读取val=1;可是thread1被抢占,让thread2运行。
thread2修改val=3,又修改回1。thread1继续执行,发现期望值与“原值”(其实被修改过了)相同,完成CAS操作。

添加额外的标记用来指示是否被修改:从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。


  • 循环时间长,开销大
    自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

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

3、两者的比较

  • 1、乐观锁并未真正加锁(不是真正的锁),效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。
  • 2、悲观锁依赖数据库锁,效率低。更新失败的概率比较低。
  • 3、悲观锁阻塞事务,乐观锁回滚重试,它们各有优缺点,不要认为一种一定好于另一种。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

参考:https://zhuanlan.zhihu.com/p/31537871
参考:https://blog.csdn.net/qq_34337272/article/details/81072874
参考:https://blog.csdn.net/qq_34337272/article/details/81072874
参考:https://mp.weixin.qq.com/s/1o-zFPBWYg4bo05MnJqvrQ
参考:https://mp.weixin.qq.com/s/QKyNL-piwFTfEDZJYSwzXA

  • 30
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
乐观锁悲观锁是并发控制的两种不同策略。乐观锁假设在竞争不激烈的情况下,冲突的概率较小,因此不加锁,只在更新数据时判断是否被其他线程更新了。乐观锁的优势在于不会阻塞其他线程的访问,提高了并发的响应速度,并且不需要消耗额外的系统资源。\[1\]\[2\]悲观锁则会在访问代码块或数据时加锁,其他线程必须等待锁的释放才能进入操作。悲观锁的优势在于可以保证数据的排他性,通过数据库的锁机制实现,如行级锁、表级锁、页级锁、共享锁和排他锁等。\[1\]\[3\]因此,乐观锁悲观锁的区别在于加锁的时机和方式,以及对并发处理速度和数据排他性的影响。 #### 引用[.reference_title] - *1* *2* [乐观锁悲观锁的区别](https://blog.csdn.net/weixin_45177786/article/details/121573184)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [悲观锁乐观锁的区别](https://blog.csdn.net/u011861874/article/details/81534718)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

还能坚持

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

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

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

打赏作者

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

抵扣说明:

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

余额充值