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

乐观锁与悲观锁

概述

乐观锁

总是假设最好的情况,每次去读数据的时候都认为别人不会修改,所以不会上锁, 但是在更新的时候会判断一下在此期间有没有其他线程更新该数据, 可以使用版本号机制和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。

免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。

传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

原文地址:https://www.cnblogs.com/yuxiang1/p/10885714.html

时间: 2024-11-05 04:05:03

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

一篇文章带你了解spring框架

虽然现在流行用SpringBoot了,很多配置已经简化和封装了,但是对于Spring的一些基础我们了解一些是对我们自己的架构思想很有帮助的!接下来和笔者一起来探讨一下Spring框架吧! 1.什么是Spring框架?Spring框架有哪些主要模块? Spring框架是一个为Java应用程序的开发提供了综合.广泛的基础性支持的Java平台.Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发.Spring框架本身亦是按照设计模式精心打造,这使得我们可以在开发环境中安

乐观锁和悲观锁 你更钟情于哪一个?

链接:http://www.csdn.net/article/2012-12-12/2812708-leguansuo-beiguansuo-couchbase :云计算 对数据库的并发访问一直是应用程序开发者需要面对的问题之一,一个好的解 决方案不仅可以提供高的可靠性还能给应用程序的性能带来提升.下面我们来看一下Couchbase产品市场经理Don Pinto结合Couchbase Server为我们带来的悲观锁和乐观锁的解析. 故事背景:Alice和Joe将共同读取Couchbase Ser

快钱支付与Sql Server的乐观锁和悲观锁

在实际的多用户并发访问的生产环境里边,我们经常要尽可能的保持数据的一致性.而其中最典型的例子就是我们从表里边读取数据,检查验证后对数据进行修改,然后写回到数据库中.在读取和写入的过程中,如果在多用户并发的环境里边,其他用户已经把你要修改的数据进行了修改是非常有可能发生的情况,这样就造成了数据的不一致性. 最近在做快钱支付的时候就碰到了这个问题,原来的代码如下:1. 表Order的结构:    OrderId   int 自增长    Status   nvarchar(10)  //未处理时的状

乐观锁和悲观锁

乐观锁和悲观锁 为什么需要锁(并发控制)? 在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突.这就是著名的并发性问题. 典型的冲突有: l 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失.例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新. l 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取.例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6. 为了解决这些并发带来的问题. 我们需要引入并发控制机

web开发中的两把锁之数据库锁:(高并发--乐观锁、悲观锁)

这篇文章讲了 1.同步异步概念(消去很多疑惑),同步就是一件事一件事的做:sychronized就是保证线程一个一个的执行. 2.我们需要明白,锁机制有两个层面,一种是代码层次上的,如Java中的同步锁,典型的就是同步关键字synchronized ( 线    程级别的).另一个就是数据库层次上的,比较典型的就是悲观锁和乐观锁. 3.常见并发同步案例分析   附原文链接 http://www.cnblogs.com/xiohao/p/4385508.html 对于我们开发的网站,如果网站的访问

总结乐观锁和悲观锁

乐观锁和悲观锁,就是对数据库进行操作时使用的,乐观锁是update是开始,悲观锁是查询记录那一刻开始,两者结束都是commit或者 rollback 悲观锁,一直锁,不让改 乐观锁,只在更新的时候判断一下别人有没有改过这个数据,保证商品只被卖出一次,可以使用版本号等机制,可以提高数据吞吐量 并发控制机制,当一个用户锁住了数据之后,其他用户就不能访问 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿

SSM(十五) 乐观锁与悲观锁的实际应用

SSM(十五) 乐观锁与悲观锁的实际应用 前言 随着互联网的兴起,现在三高(高可用.高性能.高并发)项目是越来越流行. 本次来谈谈高并发.首先假设一个业务场景:数据库中有一条数据,需要获取到当前的值,在当前值的基础上+10,然后再更新回去.如果此时有两个线程同时并发处理,第一个线程拿到数据是10,+10=20更新回去.第二个线程原本是要在第一个线程的基础上再+20=40,结果由于并发访问取到更新前的数据为10,+20=30. 这就是典型的存在中间状态,导致数据不正确.来看以下的例子: 并发所带来

乐观锁vs悲观锁

一.引言 为什么需要锁(并发控制) 在并发的环境中,会存在多个用户同时更新同一条数据,这时就会产生冲突. 冲突结果: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失. 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取. 因此为了解决上述问题,引入了并发控制机制. 乐观锁(乐观并发控制)和悲观锁(悲观并发控制)是并发控制的主要手段, 其实不仅关系型数据库中有乐观锁和悲观锁的概念,像redis,memcached等都有类似的概念.所以,不要把乐观锁和悲观锁狭隘的理解

聊聊数据库乐观锁和悲观锁,乐观锁失败后重试

在写入数据库的时候需要有锁,比如同时写入数据库的时候会出现丢数据,那么就需要锁机制. 数据锁分为乐观锁和悲观锁,那么它们使用的场景如下: 1. 乐观锁适用于写少读多的情景,因为这种乐观锁相当于JAVA的CAS,所以多条数据同时过来的时候,不用等待,可以立即进行返回. 2. 悲观锁适用于写多读少的情景,这种情况也相当于JAVA的synchronized,reentrantLock等,大量数据过来的时候,只有一条数据可以被写入,其他的数据需要等待.执行完成后下一条数据可以继续. 他们实现的方式上有所