什么是ABA问题?
简单来说就是:狸猫换太子。
假设这里有两个线程线程1和线程2,线程1工作时间需要10秒,线程2工作需要2秒,主内存值为A,第一轮线程1和线程2都把A拿到自己的工作内存,2秒中后线程2工作完成把A改成了B再写回去,又过了2秒,线程2把B改成了A再写回去,然后就线程2进入休眠状态,这时候线程1工作完成,看到期望为A真实值也是A认为没有人动过,然后线程1进行CAS操作。
ABA问题的产生
CAS会导致ABA问题。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么这个时间差会导致数据的变化。
比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存位置V中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中V的位置仍是A,然后线程1操作成功。尽管线程1的CAS操作成功,但是不代表这个过程就是没问题的。
ABA问题的解决
新增一种机制,那就是修改版本号(类似于时间戳)。
用AtomicStampedReference(int initialRef, int initialStamp),它有两个参数,第一个初始值,第二是初始版本号,它的compareAndSet方法有四个参数:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
第一个参数期望值,第二个参数跟新值,第三个参数当前版本号,第四个参数最新版本号。
代码演示:
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
// 线程1进行一次ABA操作
new Thread(() ->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"线程1").start();
new Thread(() ->{
// 线程2先暂停1秒,确保线程1能顺利完成一次ABA操作
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100,2019)+"\t"+atomicReference.get());
},"线程2").start();
// 以下是ABA问题的解决
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() ->{
// 线程2先暂停1秒,确保线程1能顺利完成一次ABA操作
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+stamp);
// 暂停1秒钟线程3
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100,101,stamp,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());
},"线程3").start();
new Thread(() ->{
// 线程2先暂停1秒,确保线程1能顺利完成一次ABA操作
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+stamp);
// 暂停3秒钟线程4,保证线程3完成一次ABA操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 修改是否成功:"+result+"\t 版本号:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t 当前实际最新值:"+atomicStampedReference.getReference());
},"线程4").start();
}
运行结果: