何为CAS的ABA问题?
一个线程T1从内存位置V中取出A,这时候另一个线程T2也从内存中取出A,并且T2进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程T1进行CAS操作发现内存中仍然是A,然后T1操作成功。尽管线程T1的CAS操作成功,但是不代表这个过程就是没有问题的。
ABA问题的根本在于CAS在修改变量的时候,无法记录变量的状态,比如修改的次数,是否修改过这个变量。这样就很容易在一个线程将A修改成B时,又再次将B修改成A,造成CAS多次执行的问题。
代码模拟ABA问题
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String args[]) {
System.out.println("模拟ABA问题:");
new Thread(() -> {
// ABA
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "T1").start();
new Thread(() -> {
try {
// 暂停一秒钟,保证T1线程干完所有事情
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + " 当前" + atomicReference.get());
}, "T2").start();
}
}
如何解决ABA问题?
使用AtomicStampedReference的compareAndSet函数,这里面有四个参数:
compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
(1)第一个参数expectedReference:表示预期值。
(2)第二个参数newReference:表示要更新的值。
(3)第三个参数expectedStamp:表示预期的时间戳。
(4)第四个参数newStamp:表示要更新的时间戳。
public class ABADemo {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String args[]) {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 第一次版本号:" + stamp);
try {
// 暂停一秒钟T3,等待T4拿到相同的版本号
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ABA
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + " 第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + " 第三次版本号:" + atomicStampedReference.getStamp());
}, "T3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 第一次版本号:" + stamp);
try {
// 暂停三秒钟T4
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println("当期T4的版本号:" + stamp);
System.out.println("当期主存的真实版本号:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "修改成功否:" + result + " 当前值:" + atomicStampedReference.getReference());
}, "T4").start();
}
}