CAS会导致“ABA问题”。
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
尽管线程one的CAS操作成功,但是不代表这个过程就是没问题的。
程序:线程A,B去操作内存值100,线程A发现内存值和预估值一样,都是100,成功修改为101,又再次改回100。
线程B发现内存值和预估值一样,也成功操作,修改为101.
public class TestAtomicReference {
private static AtomicReference<Integer> reference = new AtomicReference<>(100);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
boolean b1 = reference.compareAndSet(100, 101);
System.out.println(Thread.currentThread().getName()+" 内存值: "+reference.get());
boolean b2 = reference.compareAndSet(101, 100);
System.out.println(Thread.currentThread().getName()+" 内存值: "+reference.get());
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
//让上面的ABA问题先执行
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
boolean b1 = reference.compareAndSet(100, 101);
System.out.println(Thread.currentThread().getName()+" 内存值: "+reference.get());
System.out.println(b1);
}
},"B").start();
}
}
上面的程序就会导致ABA问题。
但是现在我们的业务不允许发生这样的问题,可以用AtomicStampedReference来解决这个问题。
程序:代码和上面意思基本一样,不同的是AtomicStampedReference加了一个版本号,来进行又一次的判断
public class TestAtomicStampedReference {
private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int stamp = stampedReference.getStamp();
System.out.println("线程A初始版本号: "+stamp);
//模拟休眠,让B线程也获取到这个版本号(初始值为1)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
boolean b1 = stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"是否成功: "+b1+" (版本号) : "+stampedReference.getStamp());
boolean b2 = stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"是否成功: "+b2+" (版本号) : "+stampedReference.getStamp());
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
int stamp = stampedReference.getStamp();
System.out.println("线程B初始版本号: "+stamp);
//让上面的ABA问题先执行
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
boolean b = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println("线程B是否修改成功: "+b);
}
},"B").start();
}
}