从哪里引出的ABA问题
CAS—>UnSafe —> CAS底层思想 —> ABA —> 原子引用更新 —>如何规避ABA问题
什么时ABA问题
CAS算法实现的一个重要前提需要取出内存中中某时刻的数据并在当下时刻进行比较并替换,如果在这个时刻时间差中会导致数据的变化。
例如:线程A从内存中读取值为1,这个时候线程2也读取了值1,线程B第一次把这个值修改为2,第二次又把这个值修改为1。然后线程A想对变量进行修改,首先进行CAS操作发现内存中的值确实是1,然后线程A操作成功。
尽管线程A的CAS操作成功,但是不代表这个中间的过程是安全的。
ABA问题例子:
package JUC;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/****
* ABA问题 相当于只管起点和终点是相同的,不过再这中间你干了什么.有可能会因为中间的过程影响什么
*
*解决ABA问题,使用 AtomicStampedReference 带版本号的源自引用类.每次对值进行更改时,版本号+1 类似乐观锁version...
*/
public class ABADemo {
volatile static AtomicReference<Integer> atomic = new AtomicReference<>(100);
volatile static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
/*******ABA问题*******/
new Thread(()->{
boolean b = atomic.compareAndSet(100, 101);
System.out.println(b);
boolean b1 = atomic.compareAndSet(101, 100);
System.out.println(b1);
System.out.println(Thread.currentThread().getName()+"最终值为"+atomic);
},"t1").start();
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e) { e.printStackTrace(); }
}
new Thread(()->{
boolean b = atomic.compareAndSet(100, 111);
System.out.println(b);
System.out.println(Thread.currentThread().getName()+"最终值为"+atomic);
},"t2").start();
ABA问题的解决方案:
使用AtomicStampedReference(可以理解为带版本号的原子操作类,每次都会比较版本是不是之前的版本,只要修改了值版本号+1)
public class ABADemo {
volatile static AtomicReference<Integer> atomic = new AtomicReference<>(100);
volatile static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp=stampedReference.getStamp();
System.out.println((Thread.currentThread().getName()+"\t第一次版本号:"+stamp));
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e) { e.printStackTrace(); }
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()));
},"t1").start();
new Thread(()->{
int stamp=stampedReference.getStamp();
System.out.println((Thread.currentThread().getName()+"\t第一次版本号:"+stamp));
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
boolean result =stampedReference.compareAndSet(100, 111,stamp,stamp+1);
System.out.println(result);
System.out.println(stampedReference.getStamp());
},"t2").start();
}
}
输出结果: