博主信息
Sky
博文
291
粉丝
0
评论
0
访问量
7267
积分:0
P豆:617

CAS之ABA问题的解决方法

2021年10月20日 20:34:32阅读数:11博客 / Sky

AtomicReference

java.util.concurrent.atomic包下的AtomicInteger类可以对整数进行包装,使得类似于i++的操作可以变成原子操作,那么对于一般的对象类型要怎么实现原子操作呢,从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性。

public class AtomicReferenceDemo {

    static AtomicReference<Student> stu = new AtomicReference<>();


    public static void main(String[] args) {

        Student lemon = new Student("lemon", 17);

        stu.set(lemon);

        Student blue = new Student("blue", 19);

        boolean res = stu.compareAndSet(lemon, blue);

        System.out.println(res + "\tstu=" + stu.get());

    }

}


运行结果:

true stu=Student{name='blue', age=19}

但是和原来使用AtomicInteger一样,CAS的时候仍旧会产生ABA问题

产生ABA问题的代码

public class ABADemo {

    public static void main(String[] args) {

        //ABA问题的代码

        AtomicReference<Integer> atomicReference = new AtomicReference<>();

        atomicReference.set(100); //原来的值是100

        new Thread(() -> {

            boolean res1 = atomicReference.compareAndSet(100, 101);

            boolean res2 = atomicReference.compareAndSet(101, 100);

            System.out.println("res1=" + res1);

            System.out.println("res2=" + res2);

        }).start();

        new Thread(() -> {

            boolean res3 = atomicReference.compareAndSet(100, 200);

            System.out.println("res3=" + res3);

        }).start();


    }


}


运行结果:res1,res2,res3都是true,这说明虽然线程t1修改了atomicReference中的值,但是由于修改后值与原来的一样,所以线程t2在判断的时候认为该值没有被修改过CAS操作成功。

AtomicStampedReference解决ABA问题

为了解决ABA问题,引入了AtomicStampedReference,AtomicStampedReference它内部不仅维护了对象值,还维护了一个时间戳(版本号)。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳。

AtomicStampedReference的构造器

在初始化的时候传入两个参数,初始化的引用值和版本号

public AtomicStampedReference(V initialRef, int initialStamp) {

    pair = Pair.of(initialRef, initialStamp);

}


AtomicStampedReference的compareAndSet方法

public boolean compareAndSet(V   expectedReference,

                             V   newReference,

                             int expectedStamp,

                             int newStamp) {

    Pair<V> current = pair;

    return

        expectedReference == current.reference &&

        expectedStamp == current.stamp &&

        ((newReference == current.reference &&

          newStamp == current.stamp) ||

         casPair(current, Pair.of(newReference, newStamp)));

}


expectedReference   : 期望值

newReference : 想要更新成的新的值

expectedStamp : 期望的版本号

newStamp : CAS操作成功要更新成的版本号

然后每次操作的时候都会先比较版本号,版本号一致才能操作成功,卖二手每次操作成功后都将版本号增加+1(版本号只加不减)

测试代码

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;

import java.util.concurrent.atomic.AtomicStampedReference;


public class ABASolve {

    public static void main(String[] args) {

        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

        int stamp = atomicStampedReference.getStamp();

        new Thread(() -> {

            System.out.println("t1线程拿到的初始版本号:" + stamp);

            System.out.println("t1线程拿到的初始值:" + atomicStampedReference.getReference());

            //

            boolean res1 = atomicStampedReference.compareAndSet(100, 101, stamp, atomicStampedReference.getStamp() + 1);

            System.out.println("修改结果:" + res1);

            System.out.println("t1线程修改之后的版本号:" + atomicStampedReference.getStamp());

            System.out.println("t1线程修改之后的值:" + atomicStampedReference.getReference());

            boolean res2 = atomicStampedReference.compareAndSet(101, 100, 2, atomicStampedReference.getStamp() + 1);

            System.out.println("修改结果:" + res2);


        }).start();

        new Thread(() -> {

            System.out.println("t2线程拿到的初始版本号:" + stamp);

            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            boolean b1 = atomicStampedReference.compareAndSet(100, 101, stamp, atomicStampedReference.getStamp() + 1);

            System.out.println("t2线程期望的版本号为" + stamp + ",t2线程当前查看主内存中atomicStampedReference真实的版本号为:" + atomicStampedReference.getStamp());

            System.out.println("t2线程CAS操作的结果为:" + b1);

        }).start();

    }

}


该代码模拟了线程1将atomicStampedReference值修改后又改回成原来的值的过程,观察版本号的变化以及最后线程2的CAS操作成功与否,运行结果如下:

t1线程拿到的初始版本号:1 
t1线程拿到的初始值:100 
修改结果:true 
t1线程修改之后的版本号:2 
t1线程修改之后的值:101 
修改结果:true 
t2线程拿到的初始版本号:1 
t2线程期望的版本号为1,t2线程当前查看主内存中atomicStampedReference真实的版本号为:3 
t2线程CAS操作的结果为:false

总结

1.AtomicReference使得对象类型可以想整数类型一样保证包装,实现原子操作

2.AtomicStampedReference在原来AtomicReference基础上加入了stamp(版本号)这一属性,每次操作成功必定增加版本号,使得在CAS的时候不会出现ABA问题(因为数据被修改过版本号肯定不一样)。

版权申明:本博文版权归博主所有,转载请注明地址!如有侵权、违法,请联系admin@php.cn举报处理!

全部评论

文明上网理性发言,请遵守新闻评论服务协议

条评论
  • jquery中文乱码:首先修改IDE项目;然后下载jquary库;接着在jquery1.6.1文件中,搜索“contentType”;最后添加“charset=UTF-8”即可。
    本篇文章给大家介绍一下npm邮箱验证。有一定参考价值,有需要朋友可以参考一下,希望对大家有所帮助。
    效率跟数据组织式有关,跟空间利用效率有关,也跟算巧妙程度有关;通常情况下,精心选择数据结构可以带来更高运行或者存储效率。
    本篇文章给大家介绍一下conda安装nodejs出现版本过低。有一定参考价值,有需要朋友可以参考一下,希望对大家有所帮助。
    本篇文章给大家介绍一下在Bootstrap开发中Tab标签页切换图表显示。有一定参考价值,有需要朋友可以参考一下,希望对大家有所帮助。
    php get参数乱码:1、使用“iconv("gb2312","UTF-8",$gonghui);”乱码;2、通过“mb_convert_encoding
    是指准确而完整描述,是一系列清晰指令,算代表着用系统描述策略机制。
    是不是遇到过bash: composer: command not found,怎么呢?下面由composer​教程栏目给大家来详细介绍该
    MySQL会出现中文乱码原因,当我们在使用MySQL数据库时候,经常会碰到乱码,看下面代码【mysql> create table test(id int,name varchar(10
    php导出excel乱码:首先打开相应PHP代码文件;然后在处理完数据后,以及输出excel文件前添加ob_end_clean函数即可乱码
    php无保存session:1、保存session文件夹;2、需要进行检查下代码是否有,要关闭【session_auto】,并在页面中上写入【session_start】。
    程序设计可分为5个步骤:1、分析,找出规律,选择,完成实际;2、设计算,即设计出和具体步骤;3、编写程序;4、运行程序,分析结果;5、编写程序文档。
    昨天遇到一个网友告知自己网站和服务器无打开需要,开始我就到底是什么,他说打开后有503错误,于是我看到后是显示"Service Unavailable"错误提示
    一:使用noConflict():舍弃$,$用jQuery代替jQuery.noConflict();二:自定义变量:舍弃$,新定义一个$y变量来代替$var $y = jQuery.noCo
    php执行时间过长:1、进行Nginx网关请求超时设置;2、进行PHP脚本执行时间上限设置。
    php验证码无显示:首先打开相应代码文件;然后在header输出前添加代码为“ob_clean();”即可验证码无显示
    angular中浏览器兼容性怎么?下面本篇文章给大家介绍一下。有一定参考价值,有需要朋友可以参考一下,希望对大家有所帮助。
    计算机世界里,数字计算,所有语言都会丢失精度,所以没有万全策,但在人力范围内,尽量。下面本篇文章就来一下js数字计算丢失精度案。
    php提示上传根目录不存在:首先赋予根目录权限,检查是否;如果没有得到,使用mkdir命令新建文件夹;最后为新创建文件夹赋予最高权限。
    PDO连接可能会出现一些,本文主要小结了一下笔者自己遇到过一些,并提供了一些