• ABA问题产生及解决方案


    1、基本的ABA问题

    在CAS算法中,需要取出内存中某时刻的数据(由用户完成),在下一时刻比较并交换(CPU保证原子操作),这个时间差会导致数据的变化。 假设有以下顺序事件: > 1、线程1从内存位置V中取出A > 2、线程2从内存位置V中取出A > 3、线程2进行了写操作,将B写入内存位置V > 4、线程2将A再次写入内存位置V > 5、线程1进行CAS操作,发现V中仍然是A,交换成功

    尽管线程1的CAS操作成功,但线程1并不知道内存位置V的数据发生过改变

    2、ABA问题示例

    ``` public class ABADemo {
    private static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
    
    public static void main(String[] args) {
    	new Thread(() -> {
    		atomicReference.compareAndSet(100, 101);
    		atomicReference.compareAndSet(101, 100);
    	},"t1").start();
    	
    	new Thread(() -> {
    		try {
    			TimeUnit.SECONDS.sleep(1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println(atomicReference.compareAndSet(100, 2019) + "	修改后的值:" + atomicReference.get());
    	},"t2").start();
    }
    

    }

    > * 初始值为100,线程t1将100改成101,然后又将101改回100
    > * 线程t2先睡眠1秒,等待t1操作完成,然后t2线程将值改成2019
    > * 可以看到,线程2修改成功
    
    输出结果:
    

    true 修改后的值:2019

    
    <h1>3、ABA问题解决</h1>
    要解决ABA问题,可以增加一个版本号,当内存位置V的值每次被修改后,版本号都加1
    
    ### AtomicStampedReference
    AtomicStampedReference内部维护了对象值和版本号,在创建AtomicStampedReference对象时,需要传入初始值和初始版本号,
    当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功
    
    #### 示例
    

    public class ABADemo {

    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);
    
    public static void main(String[] args) {
    	new Thread(() -> {
    		System.out.println("t1拿到的初始版本号:" + atomicStampedReference.getStamp());
    		
    		//睡眠1秒,是为了让t2线程也拿到同样的初始版本号
    		try {
    			TimeUnit.SECONDS.sleep(1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
    		atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
    	},"t1").start();
    	
    	new Thread(() -> {
    		int stamp = atomicStampedReference.getStamp();
    		System.out.println("t2拿到的初始版本号:" + stamp);
    		
    		//睡眠3秒,是为了让t1线程完成ABA操作
    		try {
    			TimeUnit.SECONDS.sleep(3);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("最新版本号:" + atomicStampedReference.getStamp());
    		System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "	当前 值:" + atomicStampedReference.getReference());
    	},"t2").start();
    }
    

    }

    > 1、初始值100,初始版本号1
    > 2、线程t1和t2拿到一样的初始版本号
    > 3、线程t1完成ABA操作,版本号递增到3
    > 4、线程t2完成CAS操作,最新版本号已经变成3,跟线程t2之前拿到的版本号1不相等,操作失败
    
    输出结果:
    

    t1拿到的初始版本号:1
    t2拿到的初始版本号:1
    最新版本号:3
    false 当前 值:100

    
    ### AtomicMarkableReference
    AtomicStampedReference可以给引用加上版本号,追踪引用的整个变化过程,如:A -> B -> C -> D - > A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了3次
    但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference
    AtomicMarkableReference的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过
    
    #### 示例
    

    public class ABADemo {

    private static AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(100,false);
    
    public static void main(String[] args) {
    	new Thread(() -> {
    		System.out.println("t1版本号是否被更改:" + atomicMarkableReference.isMarked());
    		
    		//睡眠1秒,是为了让t2线程也拿到同样的初始版本号
    		try {
    			TimeUnit.SECONDS.sleep(1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true);
    		atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true);
    	},"t1").start();
    	
    	new Thread(() -> {
    		boolean isMarked = atomicMarkableReference.isMarked();
    		System.out.println("t2版本号是否被更改:" + isMarked);
    		
    		//睡眠3秒,是为了让t1线程完成ABA操作
    		try {
    			TimeUnit.SECONDS.sleep(3);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("是否更改过:" + atomicMarkableReference.isMarked());
    		System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "	当前 值:" + atomicMarkableReference.getReference());
    	},"t2").start();
    }
    

    }

    > 1、初始值100,初始版本号未被修改 false
    > 2、线程t1和t2拿到一样的初始版本号都未被修改 false
    > 3、线程t1完成ABA操作,版本号被修改 true
    > 4、线程t2完成CAS操作,版本号已经变成true,跟线程t2之前拿到的版本号false不相等,操作失败
    
    输出结果:
    

    t1版本号是否被更改:false
    t2版本号是否被更改:false
    是否更改过:true
    false 当前 值:100

  • 相关阅读:
    APP_DEBUG作用
    tp字段映射机制原理
    unix、windows、mac 的换行习惯
    DroidCam 一片 红色 解决办法
    A3 A8 算法,中文简明解释
    /usr/local/mysql/bin/mysql P 3307 protocol=tcp 无法连接mysql
    [转载]网络基础:精解传输层安全协议
    无线网络加密一点漫谈
    安全模式:J2EE、Web服务和身份管理最佳实践与策略
    scp和winscp
  • 原文地址:https://www.cnblogs.com/lmj612/p/10836912.html
Copyright © 2020-2023  润新知