cas与乐观锁(jpa乐观锁)

独占锁是一种悲观锁,synchronized就是一种独占锁;它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起直到持有锁的线程释放锁。所谓乐观锁就是每次不加锁,假设没有冲突而去完成某项操作;如果发生冲突了那就去重试,直到成功为止。CAS(CompareAndSwap)是一种有名的无锁算法。CAS算法是乐观锁的一种实现。CAS有3个操作数,内…

大家好,又见面了,我是你们的朋友全栈君。

独占锁是一种悲观锁,synchronized就是一种独占锁;它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起直到持有锁的线程释放锁。

所谓乐观锁就是每次不加锁,假设没有冲突而去完成某项操作;如果发生冲突了那就去重试,直到成功为止。

CAS(Compare And Swap)是一种有名的无锁算法。CAS算法是乐观锁的一种实现。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B并返回true,否则返回false。

注:synchronized和ReentrantLock都是悲观锁。

注:什么时候使用悲观锁效率更高、什么使用使用乐观锁效率更高,要根据实际情况来判断选择。

什么是CAS机制

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

这样说或许有些抽象,我们来看一个例子:

1.在内存地址V当中,存储着值为10的变量。

cas与乐观锁(jpa乐观锁)

2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。

cas与乐观锁(jpa乐观锁)

3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

cas与乐观锁(jpa乐观锁)

4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

cas与乐观锁(jpa乐观锁)

5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

cas与乐观锁(jpa乐观锁)

6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

cas与乐观锁(jpa乐观锁)

7.线程1进行SWAP,把地址V的值替换为B,也就是12。

cas与乐观锁(jpa乐观锁)

从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

CAS的优缺点:

乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,乐观锁是对悲观锁的改进,虽然它也有缺点,但它确实已经成为提高并发性能的主要手段,而且jdk中的并发包也大量使用基于CAS的乐观锁。但它也有缺点,如下:

1.CPU可能开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用悲观锁了。

3.ABA问题。
CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。

ABA问题:

线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了。

 

cas与乐观锁(jpa乐观锁)

ABA问题处理:

思路:解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。

cas与乐观锁(jpa乐观锁)

JAVA中ABA中解决方案(AtomicStampedReference/AtomicMarkableReference)

AtomicStampedReference 本质是有一个int 值作为版本号,每次更改前先取到这个int值的版本号,等到修改的时候,比较当前版本号与当前线程持有的版本号是否一致,如果一致,则进行修改,并将版本号+1(当然加多少或减多少都是可以自己定义的),在zookeeper中保持数据的一致性也是用的这种方式;

AtomicMarkableReference则是将一个boolean值作是否有更改的标记,本质就是它的版本号只有两个,true和false,修改的时候在这两个版本号之间来回切换,这样做并不能解决ABA的问题,只是会降低ABA问题发生的几率而已;

例子:

private static AtomicStampedReference<Integer> atomicStampedRef =
        new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
    Thread main = new Thread(() -> {
        System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
        int stamp = atomicStampedRef.getStamp(); //获取当前标识别
        try {
            Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);  //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
        System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);
    },"主操作线程");

    Thread other = new Thread(() -> {
        Thread.yield(); // 确保thread-main 优先执行
atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
        System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
        atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
        System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
    },"干扰线程");

    main.start();
    other.start();
}

文章参考:https://www.jianshu.com/p/ae25eb3cfb5d

https://blog.csdn.net/justry_deng/article/details/83449038

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/129357.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 两款实用的DDos攻击工具[通俗易懂]

    两款实用的DDos攻击工具[通俗易懂]两款实用的DDos攻击工具2018-02-22Network•Security865之前为了重现某个bug,需要对网络设备进行ddos攻击测试,同时也是对设备的网络攻击防护功能进行抗压测试。临阵磨枪,google了两款攻击工具,windows平台的hyenae,以及Linux平台的hping3,在此记录一下两者的用法。Hyenaehyenae是在Windows平台上非常好用的一…

  • stm32cubemx软件库_STM32cube

    stm32cubemx软件库_STM32cube前言:本系列教程将HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用在我们的HAL库中,对硬件SPI函数做了很好的集成,使得之前SPI几百行代码,在HAL库中,只需要寥寥几行就可以完成那么这篇文章将带你去感受下它的优异之处,这些优异的函数,也正是HAL库的优点所在所用工具:1、芯片:STM32F103ZET62、STM32CubeMx软件3、IDE:…

  • 基于rgbd的三维重建_3d map generator 教程

    基于rgbd的三维重建_3d map generator 教程Open3d-doc-RGBD场景重建

  • 怎么转换成html格式_html怎么转换成http

    怎么转换成html格式_html怎么转换成http工作中有些时候,领导下达指令需要将html格式的内容转换成Word格式的文档,对于很多新手来说,就着实不知道该如何下手了,如果是一个一个转换的话,内容过多就很艰难,而且十分耽误工作效率,那么html该怎么转换成word呢?方法一:1.首先在桌面新建一个空白的Word文档并打开。2.找到需要转换的HTML文件,可以看到该文件即为HTML格式。3.点击Word左上角的文件标志,选择打开按钮,找到HTM…

    2022年10月11日
  • Android Hook技术详解

    Android Hook技术详解代理模式详解,动态代理原理分析,AndroidHook技术详解以及其在插件化,性能优化上的一些案例分析。

  • OpenCV中的width和widthstep[通俗易懂]

    OpenCV中的width和widthstep[通俗易懂]一是width属性;二是widthStep属性。     前者是表示图像的每行像素数,后者指表示存储一行像素需要的字节数。     在OpenCV里边,widthStep必须是4的倍数,从而实现字节对齐,有利于提高运算速度。     如果8U单通道图像宽度为3,那么widthStep是4,加一个字节补齐。这个图像的一行需要4个字节,只使用前3个,最后一个空着。

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号