理解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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • pycharm每次新建项目都要重新安装一些第三方库解决办法

    pycharm每次新建项目都要重新安装一些第三方库解决办法目前有三个解决办法,也是亲测有用的:第一个方法:因为之前有通过pycharm的projectinterpreter里的+号添加过一些库,但添加的库只是指定的项目用的,如果想要用,就必须用之前的项目的python解释器,举个例子:这个是我之前的项目的解释器,这个项目解释器是继承的python的解释器,同时又安装了上面你看到的这些库,包含numpy和opencv-python等,然后我新…

    2022年5月17日
    41
  • eclipse汉化小教程

    eclipse汉化小教程点aboutEclipsexxx然后会显示版本http://www.eclipse.org/babel/downloads.php进入该网址一般都选择Mars(最后一个)复制网址http://download.eclipse.org/technology/babel/update-site/R0.15.1/mars加载出pending包后选择ChineseTraditional安装一路n…

    2022年5月3日
    66
  • Photoshop快捷键大全_alt快捷键大全常用

    Photoshop快捷键大全_alt快捷键大全常用察看图像类别  说明:: —Shift键  :—空格键       *—在Imageready中不适用  §—只在Imageready中可用动作结果双击工具箱::或Ctrl+0 使图像最大限度在当前窗口中完整显示双击工具箱::或Alt+Ctr

    2022年9月29日
    4
  • SSH会话连接超时问题

    SSH会话连接超时问题

    2021年10月7日
    61
  • 安装VM虚拟机教程

    安装VM虚拟机教程虚拟机安装及新建指南安装 VM 虚拟机创建虚拟机开启虚拟机安装 VM 虚拟机待定创建虚拟机 1 新建虚拟机 2 默认选择自定义 直接下一步 3 默认点击下一步 4 默认点击下一步 5 这里我选择的是 Linux 系统 默认点击下一步 6 更改虚拟机名称及安装位置 7 选择处理器数量 8 选择虚拟机内存 9 默认下一步 10 默认下一步 11 默认下一步 12 默认下一步 13 分配磁盘大小 14 磁盘文件名 默认下一步 15 选择自定义硬件 16 选择使用的镜像文件 17 点击完成

    2025年8月18日
    6
  • linux下如何启动ice服务器,Linux下ICE的安装[通俗易懂]

    linux下如何启动ice服务器,Linux下ICE的安装[通俗易懂]ICE在Linux下的完整编译安装安装平台要求:最好用gcc4.x版编译ICE,在Slackware下发现gcc3.3.6和gcc3.4.6都无法编译通过为了方便管理,将ICE相关的软件都安装到/usr/local/ICE-3.3.0/目录下首先安装第三方包:ThirdParty-Sources-3.3.0.tar.gz解压ThirdParty-Sources-3.3.0.tar.gz#c…

    2022年5月29日
    87

发表回复

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

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