乐观锁与悲观锁

概述

乐观锁

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

悲观锁

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

使用场景

  • 乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

  • 悲观锁适用于读比较少的情况下(多写场景),如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

乐观锁好比生活中乐观的人总是想着事情往好的方向发展,悲观锁好比生活中悲观的人总是想着事情往坏的方向发展。 这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。

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

版本控制

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

举个例子:

假设数据库中帐户信息表中有一个 version 字段,并且 version=1;而当前帐户余额字段(balance)为 $100 。操作员 A 此时将其读出 (version=1),并从其帐户余额中扣除 $50($100-$50)。操作员 A 操作的同事,操作员B 也读入此用户信息(version=1),并从其帐户余额中扣除 $20($100-$20)。操作员 A 完成了修改工作,version++(version=2),连同帐户扣除后余额(balance=$50),提交至数据库更新,
此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。操作员 B 完成了操作,也将版本号version++(version=2)试图向数据库提交数据(balance=$80),
但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,
不满足**提交版本必须大于记录当前版本才能执行更新**的乐观锁策略,因此,操作员 B 的提交被驳回。复制代码

避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

CAS算法

硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。 CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。 当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

//著名的CAS
//var1是比较值所属的对象,var2需要比较的值(但实际是使用地址偏移量来实现的),
//如果var1对象中偏移量为var2处的值等于var4,那么将该处的值设置为var5并返回true,如果不等于var4则返回false。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);复制代码

乐观锁的缺点

1.ABA问题

如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。

J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题, 它可以通过控制变量值的版本来保证 CAS 的正确性。 大部分情况下 ABA 问题不会影响程序并发的正确性, 如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。

2.自旋时间长开销大

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

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

CAS与synchronized的使用情景

  • 对于资源竞争较少(线程冲突较轻)的情况, 使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源; 而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

  • 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大, 从而浪费更多的CPU资源,效率低于synchronized。

转载于:https://juejin.im/post/5cdfa98c51882525f52cf6f9

一篇文章带你解析,乐观锁与悲观锁的优缺点相关推荐

  1. 一文彻底理解乐观锁与悲观锁

    通过阅读本文可以获得什么 1.什么是乐观锁? 2.乐观锁实现方式都有什么? 3.乐观锁优缺点有哪些? 4.乐观锁适用场景? 5.什么是悲观锁? 6.悲观锁实现方式有哪几种? 7.悲观锁优缺点? 8.悲 ...

  2. 一篇文章带你弄懂乐观锁与悲观锁,CAS原子性,synchronized底层原理

    文中加入了个人理解,如有不准确的地方欢迎提出,笔者会及时的进行改正. 乐观锁与悲观锁 乐观锁: 假设数据不会发生冲突,只有在进行数据更新的才会对数据进行检查,如果冲突则更新失败并返回错误信息 悲观锁: ...

  3. 一篇文章带你详解 TCP/IP 协议(下)

    前面的第一二三章已在上篇讲解,还没看过的可以先看看:一篇文章带你详解 TCP/IP 协议(上) 本文继续讲解第四章. 四.网络层中的 IP 协议 IP(IPv4.IPv6)相当于 OSI 参考模型中的 ...

  4. 一篇文章带你详解 HTTP 协议(下)

    文章目录,方便阅读: 一.概述(已讲) 二.HTTP 工作过程(已讲) 三.HTTP 协议基础(已讲) 四.HTTP 协议报文结构(已讲) 五.HTTP 报文首部之请求行.状态行(已讲) 六.HTTP ...

  5. 一篇文章带你熟悉 TCP/IP 协议(网络协议篇二)

    涤生_Woo 2017年11月11日阅读 15544 关注 一篇文章带你熟悉 TCP/IP 协议(网络协议篇二) 同样的,本文篇幅也比较长,先来一张思维导图,带大家过一遍. 一图看完本文 一. 计算机 ...

  6. 一篇文章带你领悟 Frida 的精髓(基于安卓8.1)

    转载(一篇文章带你领悟Frida的精髓(基于安卓8.1)):https://www.freebuf.com/articles/system/190565.html <Frida操作手册>: ...

  7. 一篇文章带你熟悉 TCP/IP 协议-(三)

    一篇文章带你熟悉 TCP/IP协议-(一)-https://segmentfault.com/a/11... 一篇文章带你熟悉 TCP/IP协议-(二)-https://segmentfault.co ...

  8. 乐鑫esp8266学习rtos3.0笔记第3篇: 一篇文章带你搞掂存储技术 NVS 的认识和使用,如何利用NVS保存整型、字符串、数组以及结构体。(附带demo)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. Esp8266之 搭建开发环境,开始一个" ...

  9. 一篇文章带你详解 HTTP 协议

    一篇文章带你详解 HTTP 协议 本篇文章篇幅比较长,先来个思维导图预览一下. 一张图带你看完本篇文章 一.概述 1.计算机网络体系结构分层 计算机网络体系结构分层 2.TCP/IP 通信传输流 利用 ...

最新文章

  1. FOSCommentBundle功能包:设置Doctrine ODM映射(投票)
  2. 统计学中的协方差矩阵(阵列信号基础)
  3. python计算出nan_python如何进行汇总统计?
  4. IDEA工具Terminal使用git log中文乱码的解决方法
  5. 很酷的一套 Flex/AIR 皮肤 (KingnareStyle)
  6. oracle会闪,oracle闪来
  7. Spring Bean的实例化时机与ApplicationContext中单例bean的延迟初始化
  8. centec交换机配置_盛科(Centec)交换机 SmartConfig 特性
  9. 微软发布ASP.NET 5路线图
  10. C++开发的应用方向有哪些?
  11. 保险科技服务商豆包网完成9500万新一轮融资,博将资本领投
  12. +0.5(加0.5)配合int()实现四舍五入
  13. matlab遗传算法输出参数太多,用遗传算法工具箱时错误提示太多输出参数?
  14. 2014武汉理工计算机专业李帅,武汉理工大学信息工程学院2014考研复试名单公示...
  15. JHOST邀请码,2012年7月31日申请,2012年8月31日过期
  16. Android - BGAQRCode 扫描二维码生成二维码
  17. 60级神圣系圣骑心得(转)
  18. bwa mem 报错处理:[mem_sam_pe] paired reads have different names
  19. 曲面积分的投影法_在家学|第一类曲面积分与第二类曲面积分的计算
  20. git 怎么回退已经push的版本_Git版本回退

热门文章

  1. 全球及中国有色金属行业未来发展走势与投资机遇研究报告2022版
  2. 中国学前教育行业投资机会评估与运营展望规划报告2022版
  3. 中国棉纺织工业发展状况及未来竞争格局报告2022-2028年
  4. 全球及中国固体真空重合器行业深度调研及竞争格局展望报告2021-2027年版
  5. 当引用com类dll时,在VS2005下会出现,dll虽然更换了但是引用没有更换或找不到的错误
  6. 洛谷 4568 [JLOI2011] 飞行路线
  7. 使用DeviceOne实现微信小程序功能
  8. Retrofit 2使用要点梳理:小白进阶回忆录
  9. 数据库空值(Null)小结
  10. 产品经理的高阶能力:商业思维基于商业画布的研习方法论