理解ABA问题_什么叫ABA

理解ABA问题_什么叫ABA理解ABA问题1ABA问题的产生2原子引用AtomicReference3带时间戳的原子引用AtomicStampedReference解决ABA问题1ABA问题的产生所谓ABA问题,就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。比如线程1和线程2同时也从内存取出A,线程T1将值从A改为B,然后又从B改为A。线程T2看到的最终值还是A,经过与预估值的比较,二者相等,可以更新,此时尽管线程T2的CAS操作成功,但不代表就没有问题。有的需求,比如CAS,只注重头

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定


1 ABA问题的产生

所谓ABA问题,就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。比如线程1和线程2同时也从内存取出A,线程T1将值从A改为B,然后又从B改为A。线程T2看到的最终值还是A,经过与预估值的比较,二者相等,可以更新,此时尽管线程T2的CAS操作成功,但不代表就没有问题。

有的需求,比如CAS,只注重头和尾的一致,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改,这就引出了AtomicReference原子引用


2 原子引用 AtomicReference

AtomicInteger对整数进行原子操作,AtomicInteger对长整型数进行原子操作,AtomicBoolean对布尔型数进行原子操作,但实际上这些是完全不够的,如果是一个POJO呢?可以用AtomicReference来包装这个POJO,使其操作原子化

Class AtomicReference < V >,Value就是我们需要进行原子包装的泛型类。

示例:

@Getter
@ToString
@AllArgsConstructor
class User { 
   
    String userName;
    int age;
}

public class AtomicRefrenceDemo { 
   
    public static void main(String[] args) { 
   
        User z3 = new User("张三", 22);
        User l4 = new User("李四", 23);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(z3);
        System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
    }
}

Jetbrains全家桶1年46,售后保障稳定

输出结果:

true User(userName=李四, age=23)
false User(userName=李四, age=23)

那么我们如何在原子引用的基础上,解决ABA问题呢,请看带时间戳的原子引用 AtomicStampedReference。


3 带时间戳的原子引用 AtomicStampedReference 解决ABA问题

使用AtomicStampedReference类可以解决ABA问题。这个类维护了一个“版本号”Stamp“,在进行CAS操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。

核心方法:

static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(initialRef, initialStamp);
int stamp = atomicStampedReference.getStamp()
AtomicStampedReference.compareAndSet(expectedReference,newReference,oldStamp,newStamp);

示例:

public class ABADemo { 
   
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) { 
   
        System.out.println("=====以下时ABA问题的产生=====");
        new Thread(() -> { 
   
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "Thread 1").start();

        new Thread(() -> { 
   
            try { 
   
                //保证线程1完成一次ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) { 
   
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
        }, "Thread 2").start();
        try { 
   
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) { 
   
            e.printStackTrace();
        }
        System.out.println("=====以下时ABA问题的解决=====");

        new Thread(() -> { 
   
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
            try { 
   
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) { 
   
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp());
        }, "Thread 3").start();

        new Thread(() -> { 
   
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
            try { 
   
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) { 
   
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

            System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference());
        }, "Thread 4").start();
    }
}

输出结果:

===== 以下时ABA问题的产生 =====
true 2019
===== 以下时ABA问题的解决 =====
Thread 3 第1次版本号1 //初始版本号
Thread 4 第1次版本号1 //初始版本号
Thread 3 第2次版本号2 //第一次修改后的版本号
Thread 3 第3次版本号3 //第二次修改后的版本号
Thread 4 修改是否成功false 当前最新实际版本号:3 //修改失败,此时T4的版本号为1+1,但实际T3已经将版本号增加到了3,T4修改失败
Thread 4 当前最新实际值:100

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

T3线程拿到第一次版本号后睡眠2秒,保证T4线程能拿到和它一样的初始版本号。

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

T4线程拿到第一次版本号后再睡眠4秒,保证在此期间T3线程已经完成了一次ABA操作。

atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

第一个参数代表预估值,第二个参数代表更新值,第三个参数代表预估版本号,第四个参数代表更新版本号。
如果预估值与内存实际值相等,预估版本号与实际版本号相等,则更新内存值为更新值,更新版本号为更新版本号。

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

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/234260.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • postman汉化包_python模拟post请求

    postman汉化包_python模拟post请求Postman安装(汉化Postman)一、下载Postman下载地址:https://www.postman.com/downloads/二、下载汉化包下载地址:https://github.com/hlmd/Postman-cn/releases注意:中文包的版本和postman的版本一定要一致,否则会出现汉化后打不开postman的情况postman设置里能看到版本号:汉化包下对应的就可以:三、解压到对应目录四、重启P…

    2022年9月30日
    0
  • 0基础如何自学软件编程开发

    0基础如何自学软件编程开发0基础如何自学软件编程开发?学习软件编程首先需要选择一门编程语言,如C或JAVA语言,作为基础编程语言学习,掌握语言的逻辑,学习语法,其实编程实质上就是思路的运用,编程思路有了再想学习其他的编程语言就会变得顺风顺水。软件编程开发,对于现在的学生来讲到底有多重要呢?现在是互联网快速发展的时期,在几年前谁都没有想到人们在手机上就可以完成衣食住行等所有的活动,互联网也在慢慢的改变着未来一代人。互联网广泛覆盖了我们的生活,真正实现了“远在天边,近在眼前”,在我们的生活工作中都有互联网存在的身影,随着IT行业的越

    2022年5月7日
    74
  • 真正的自重…小偷可以灭绝

    真正的自重…小偷可以灭绝

    2021年7月29日
    63
  • 镁光闪存颗粒对照表_最全的内存颗粒编码规则说明,教你看穿内存条到底用的什么颗粒…[通俗易懂]

    镁光闪存颗粒对照表_最全的内存颗粒编码规则说明,教你看穿内存条到底用的什么颗粒…[通俗易懂]今天我们一起来了解、学习下海力士、南亚、镁光内存颗粒的编码规则,以帮助我们快速的看穿内存条到底使用的是什么颗粒,颗粒的质量和性能如何。海力士内存颗粒编码规则以苹果DDR42666MHz64GB(2*32GB)笔记本内存条为例,价格8802元。这款内存条采用的颗粒是海力士的H5ANAG8NMJRVKC。海力士DDR4颗粒编码一共14位。前4位基本是固定的,不用看,我们看后10位——AG8NMJR…

    2022年6月22日
    524
  • 管理sql server表数据_sql server如何使用

    管理sql server表数据_sql server如何使用表是SQL Server中最基本的数据库对象,用于存储数据的一种逻辑结构,由行和列组成, 它又称为二维表。例如,在学生成绩管理系统中,表1–是一个学生表(student)。(1)表表是数据库中存储数据的数据库对象,每个数据库包含了若干个表,表由行和列组成。例如,表1–由6行6列组成。(2)表结构每个表具有一定的结构,表结构包含一组固定的列,由数据类型、长度、允许Null值等组成…

    2022年8月18日
    13
  • ORA-01017: invalid username/password; logon denied Oracle数据库报错解决方案一[通俗易懂]

    ORA-01017: invalid username/password; logon denied Oracle数据库报错解决方案一[通俗易懂]ORA-01017:invalidusername/password;logondenied错误(程序中的用户和密码无法登录,登录被拒)。Oracle11g版本初次安装使用报错:解决方法1创建新用户:打开sqlplus以系统身份登录:指令如下sys/managerassysdba;创建新用户:语法:createuser用户名identifiedb…

    2022年5月6日
    648

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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