aba会导致问题_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 atomicReference = new AtomicReference(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) + "\t修改后的值:" + atomicReference.get());

},"t2").start();

}

}

> * 初始值为100,线程t1将100改成101,然后又将101改回100

> * 线程t2先睡眠1秒,等待t1操作完成,然后t2线程将值改成2019

> * 可以看到,线程2修改成功

输出结果:

true修改后的值:2019

3、ABA问题解决

要解决ABA问题,可以增加一个版本号,当内存位置V的值每次被修改后,版本号都加1

### AtomicStampedReference

AtomicStampedReference内部维护了对象值和版本号,在创建AtomicStampedReference对象时,需要传入初始值和初始版本号,

当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功

#### 示例

public class ABADemo {

private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(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) + "\t当前 值:" + 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 atomicMarkableReference = new AtomicMarkableReference(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) + "\t当前 值:" + 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值