1、ABA问题示例
package com.example.sgg.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* ABA问题
* Created by 奔跑的蜗牛 on 2022/5/5 0005.
* 每天学习一点点,每天进步一点点
*/
public class ABADemo {
// 主内存共享变量,初始值为1,版本号为1
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
// t1,期望将1改为10
new Thread(() -> {
// 第一次拿到的时间戳
int value = atomicInteger.get();
System.out.println(Thread.currentThread().getName() + " 第1次拿到值为:" + value);
// 休眠5s,确保t2执行完ABA操作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// t2将时间戳改为了3,cas失败
boolean b = atomicInteger.compareAndSet(value, 10);
System.out.println(Thread.currentThread().getName() + " CAS是否成功:" + b);
System.out.println(Thread.currentThread().getName() + " 当前最新值为:" + atomicInteger.get());
}, "t1").start();
// t2进行ABA操作
new Thread(() -> {
// 第一次拿到的时间戳
int value = atomicInteger.get();
System.out.println(Thread.currentThread().getName() + " 第1次拿到值为:" + value);
// 休眠,修改前确保t1也拿到同样的副本,初始值为1
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将副本改为20,再写入,紧接着又改为1,写入,每次提升一个时间戳,中间t1没介入
atomicInteger.compareAndSet(value, 20);
System.out.println(Thread.currentThread().getName() + " 第2次拿到值为:" + atomicInteger.get());
atomicInteger.compareAndSet(20, value);
System.out.println(Thread.currentThread().getName() + " 第3次拿到值为:" + atomicInteger.get());
}, "t2").start();
}
}
2、ABA问题解决示例
package com.example.sgg.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 解决ABA问题
* Created by 奔跑的蜗牛 on 2022/5/5 0005.
* 每天学习一点点,每天进步一点点
*/
public class ABASolveDemo {
// 主内存共享变量,初始值为1,版本号为1
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
public static void main(String[] args) {
// t1,期望将1改为10
new Thread(() -> {
// 第一次拿到的时间戳
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 第1次时间戳:" + stamp + " 值为:" + atomicStampedReference.getReference());
// 休眠5s,确保t2执行完ABA操作
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// t2将时间戳改为了3,cas失败
boolean b = atomicStampedReference.compareAndSet(1, 10, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + " CAS是否成功:" + b);
System.out.println(Thread.currentThread().getName() + " 当前最新时间戳:" + atomicStampedReference.getStamp() + " 最新值为:" + atomicStampedReference.getReference());
}, "t1").start();
// t2进行ABA操作
new Thread(() -> {
// 第一次拿到的时间戳
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 第1次时间戳:" + stamp + " 值为:" + atomicStampedReference.getReference());
// 休眠,修改前确保t1也拿到同样的副本,初始值为1
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将副本改为20,再写入,紧接着又改为1,写入,每次提升一个时间戳,中间t1没介入
atomicStampedReference.compareAndSet(1, 20, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + " 第2次时间戳:" + atomicStampedReference.getStamp() + " 值为:" + atomicStampedReference.getReference());
atomicStampedReference.compareAndSet(20, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + " 第3次时间戳:" + atomicStampedReference.getStamp() + " 值为:" + atomicStampedReference.getReference());
}, "t2").start();
}
}
简单理解就是AtomicStampedReference使用了单向版本号,不可逆转,每次修改则版本号递增,以此来解决ABA的问题
3、ABA问题带来的危害
个人理解:
1、一条线程造成的ABA问题对业务的数据不会造成影响,因为最终一致,可以看做这个ABA为回滚动作;
举例说明:
一个线程同时做了,购票(票数减一)和退票操作(票数加一),就算是发生了ABA,而另一个购票线程依然还是根据旧的票数减一操作,最终结果不受影响(此处为举例,实际购票退票理论上不是一个线程做的)
2、对于两条以上线程造成的ABA问题对业务数据会造成影响,因为影响的最终一致性;
举例说明:
小牛取款,由于机器不太好使,多点了几次取款操作。后台threadA和threadB工作,
此时threadA操作成功(100->50),threadB阻塞。正好牛妈打款50元给小牛(50->100),
threadC执行成功,之后threadB运行了,又改为(100->50), 钱变少了,因为原本多点击的threadB是无法执行的,因此牛妈打款后,最终金额应该是100。