博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
乐观锁悲观锁对应的JAVA代码和数据库
阅读量:6974 次
发布时间:2019-06-27

本文共 2366 字,大约阅读时间需要 7 分钟。

乐观锁

悲观锁
是一种思想。可以用在很多方面。

比如数据库方面。

悲观锁就是for update
乐观锁就是 version字段

JDK方面:

悲观锁就是sync
乐观锁就是原子类(内部使用CAS实现)

本质来说,就是悲观锁认为总会有人抢我的。

乐观锁就认为,基本没人抢。

 

适用场景:

悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。

乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。

总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。

 

 

乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。
作者:赵云涛
链接:https://www.zhihu.com/question/23284469/answer/24184781
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

独占锁(悲观锁)与乐观锁

在多线程编程的时候,为了保证多个线程对一个对象可以安全进行访问时,我们需要加同步锁synchronized,保证对象的在使用时的正确性,synchronized就是一种独占锁,它会导致所有需要此锁的线程挂起,等待锁的释放。

加锁会导致一下问题:

  1. 加多线程竞争下,加锁和释放锁会导致较多的上下文切换,引起性能问题。
  2. 多线程可以导致死锁的问题。
  3. 多线程持有的锁会导致其他需要此锁的线程挂起。

乐观锁(CAS)的思想却是不加锁,那不加锁如何确保某一变量的操作没有被其他线程修改过?

这里就需要CAS操作(CompareAndSwap)来实现。

CAS有三个操作参数:内存地址,期望值,要修改的新值,当期望值和内存当中的值进行比较不相等的时候,表示内存中的值已经被别线程改动过,这时候失败返回,只有相等时,才会将内存中的值改为新的值,并返回成功。

在JVM中的CAS操作就是基于处理器的CMPXCHG汇编指令实现的,因此,JVM中的CAS的原子性是处理器保障的。

这里我们可以看一下JAVA的原子类AtomicLong.getAndIncrement()的实现,来理解一下CAS这一乐观锁(JDK 1.8)。

public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L); }

接着看一下 Unsafe.getAndAddLong()的实现:

public final long getAndAddLong(Object var1, long var2, long var4) { long var6; do { var6 = this.getLongVolatile(var1, var2); } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); return var6; }

这里我们可以看到AtomicLong.getAndIncrement()的实现就是通过CAS循环操作的实现,只有期望值与真实值相同情况下,CAS操作才会成功执行,退出循环,如果失败则继续自旋,直到成功。

ABA问题

ABA问题是指在CAS操作时,其他线程将变量值A改为了B,但是又被改回了A,等到本线程使用期望值A与当前变量进行比较时,发现变量A没有变,于是CAS就将A值进行了交换操作,但是实际上该值已经被其他线程改变过,这与乐观锁的设计思想不符合。ABA问题的解决思路是,每次变量更新的时候把变量的版本号加1,那么A-B-A就会变成A1-B2-A3,只要变量被某一线程修改过,改变量对应的版本号就会发生递增变化,从而解决了ABA问题。在JDK的java.util.concurrent.atomic包中提供了AtomicStampedReference来解决ABA问题,该类的compareAndSet是该类的核心方法,实现如下:

public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair
current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }

我们可以发现,该类检查了当前引用与当前标志是否与预期相同,如果全部相等,才会以原子方式将该引用和该标志的值设为新的更新值,这样CAS操作中的比较就不依赖于变量的值了。

 

本文转自https://zhuanlan.zhihu.com/p/28049542 感谢作者

你可能感兴趣的文章
CoreOS实践指南(五):分布式数据存储Etcd(上)
查看>>
系统学习redis之一——基础概念
查看>>
form属性method="get"/"post"的两种方式对比
查看>>
老李推荐:第8章2节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-解析处理命令行参数 3...
查看>>
nload命令
查看>>
SpringMVC基础框架搭建
查看>>
在线帮助你修改图片背景的工具 - Clipping Magic
查看>>
zuul转发的一些常见异常
查看>>
聊聊spring cloud gateway的GatewayFilter
查看>>
监控zabbix3.4 +granfana5.1.3
查看>>
Linux 第六周学习笔记(1) kickstart脚本 pxe网络安装服务
查看>>
MySQL数据类型
查看>>
vim编辑模式
查看>>
Linux常用命令——sudo
查看>>
iptables-ipset模块
查看>>
Java Web分页设计实现,基于pager-taglib+Filter
查看>>
mysql加密解密方式用法
查看>>
linux sed命令详解
查看>>
javascript 多维数组
查看>>
5.Java变量
查看>>