何为ABA问题?
我们在使用CAS时,当两个线程要修改同一个值,第一个修改初始数值为100,而第二个线程修改初始数值100为101,然后又将101修改为100。如果第二个进行了100->101->100这个过程的修改后,第一个线程再修改,此时就出现了ABA问题,第一个线程修改的100不是原来的100,而是被第二个线程修改过的100。
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
//以下是ABA问题的产生
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, 2021) + "\t" + atomicReference.get());
},"t2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
我们发现它成功修改为2021,而此刻的100时被第一个线程修改过的,不是原来的那个值,这就是ABA问题。
那么我们该如何解决这个问题呢?
我们可以加个版本号,这样就可以通过版本号来判断它是否被修改过。
示例代码:
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
//以下是ABA问题的解决
System.out.println("==========================以下是ABA问题的解决==========================");
new Thread(() -> {
//获取初始版本号
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp );
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改原来的值,然后再修改为原来的值
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());
},"t3").start();
new Thread(() -> {
//获取初始版本号
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp );
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp() );
//比较并交换,若版本号不是初始版本号,则返回false,并且不会修改该对象的值
boolean result = atomicStampedReference.compareAndSet(100,2021,stamp,stamp+1);
System.out.println("是否修改成功:" + result);
},"t4").start();
}
运行结果:
总结:
CAS会引发ABA问题,在工作中,如果应用环境允许出现ABA问题,我们可以忽略这个问题不去加版本号,用AtomicReference即可,若无法忽略,则使用AtomicStampedReference。