CAS带来的ABA问题以及解决方案
CAS带来的ABA问题
线程1和线程2同时对变量i = 1 进行操作,线程1对i进行了两次操作,先将i+1 写出,后又进行了i-1并写出,线程2过来读的时候与原来值1仍然是相等的,虽然值仍然相等,但是i的值却发生过改变,期间从1变成2又变成1(ABA问题)。
解决方案
对值的每次操作都进行记录,需要保证是一个原子操作。
java中使用AtomicMarkableReference或者AtomicStampedReference。
AtomicMarkableReference:使用boolean表示是否修改过。
AtomicStampedReference: 使用int 记录改变的次数(推荐)!
AtomicStampedReference 使用:
class Stu {
private String name;
private Integer age;
getter/setter
}
//参数1: 对象
//参数2:初始版本号
AtomicStampedReference<Stu> accountBalance = new AtomicStampedReference<>(stu, 0);
源码:
public AtomicStampedReference(V var1, int var2) {
this.pair = AtomicStampedReference.Pair.of(var1, var2);
}
static <T> Pair<T> of(T var0, int var1) {
return new Pair(var0, var1); //底层使用Pair对象。
}
private Pair(T var1, int var2) {
this.reference = var1; //真实对象
this.stamp = var2; //版本号
}
Thread threadA = new Thread(() -> {
int[] stampHolder = new int[1];
//获取对象
Stu stu1 = accountBalance.get(stampHolder);
//改变的次数
int currentStamp = stampHolder[0];
System.out.println("Thread A: 当前年龄 = " + stu1.getAge() + ", 当前版本号= " + currentStamp);
stu1.setAge(100000);
boolean success = accountBalance.compareAndSet(stu1, stu1, currentStamp, currentStamp + 1);
/**
*源码:
* 参数1: 期望值
* 参数2: 新值
* 参数3: 原有修改次数
* 参数4: 新修改次数(一般使用原有修改次数+1)
* public boolean compareAndSet(V var1, V var2, int var3, int var4) {
*
* //原始对象值 包含对象和修改的次数
* Pair var5 = this.pair;
* return
* var1 == var5.reference //对象不是同一个(期望值不一样) 就不再进行操作
* && var3 == var5.stamp // 版本号不一样 就不再进行操作
* && (var2 == var5.reference && var4 == var5.stamp //操作对象与原来一样并且操作次数也一样 表示中间的值没有被其他线程修改过,无需继续修改
* ||
* this.casPair(var5, AtomicStampedReference.Pair.of(var2, var4))); //进行CAS操作
* }
*
*
*
*
*
*/
if (success) {
System.out.println("Thread A: 更新成功,新的年龄 = " + accountBalance.getReference().getAge() + ",新的版本号" + accountBalance.getStamp());
} else {
System.out.println("Thread A: 更新失败");
}
});