ABA问题
比如有两个线程,线程T1和线程T2。线程T1的执行时间短(假如需要2s),线程T2的执行时间长(假如需要4s),也即是线程T2执行一次的时间,线程T1可以执行多次。
当线程T1将变量从A变成B,又从B变成A,这对线程T2而言,它是不知道该变量是否发生过变化的。可能会导致不可预知的问题。
通过原子类举例
public static void main(String[] args) {
AtomicInteger num=new AtomicInteger(0);
//如果num的当前值是0 则把它更新为1
System.out.println(num.compareAndSet(0, 1));
//如果num的当前值是1 则把它更新为0
System.out.println(num.compareAndSet(1, 0));
//打印num当前值
System.out.println(num.get());
}
num的数据从0–>1,又从1–>0。不能说num没有变化过。
ABA问题的解决
通过借鉴乐观锁的思路,为数据添加上版本号来处理这个问题。JUC包中也提供了对应的解决方案AtomicStampedReference
。这个类维护了一个“版本号”Stamp,在进行CAS操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。
public class StampDemo {
private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
try {
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号: " + stamp);
Thread.sleep(2 * 1000);
stampedReference.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号: " + stampedReference.getStamp());
stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第一次版本号: " + stampedReference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
Thread.sleep(4 * 1000);
boolean result = stampedReference.compareAndSet(100, 2019, stampedReference.getStamp(), stampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功与否:" + result + " 当前最新版本号" + stampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前实际值:" + stampedReference.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
输出结果:
T1 第一次版本号: 1
T1 第二次版本号: 2
T1 第一次版本号: 3
T2 修改成功与否:true 当前最新版本号4
T2 当前实际值:2019