从一个AtomicInteger原子类,我们可以引申出以下一系列知识点,可以打包一起记忆:

一、什么是ABA问题?
一句话:狸猫换太子;
有A、B两个线程,线程A执行一次CAS操作需要用时10s,但是B线程执行一次CAS操作只需要2s,主内存中的原始值为1,两个线程开始时候都获取到了原始值1,但是在由于B线程很快,它先将value值修改为了2,过一会儿又修改为了1,此时对于A线程来说,它在执行compareAndAddInt方法的时候,发现前后两个数据都是1,就很乐观地认为主存中的value值没有被其他线程修改过,顺利成功执行了compareAndAddInt方法;这种问题就是所谓的ABA问题(在一个线程执行CAS的过程中,另一个线程快速地修改了主存中的value并又快速地修改回来,从而导致第一个线程误以为没有其他线程修改过value值的问题);
ABA问题是CAS的最大问题所在,必须解决;
如何解决ABA问题,需要引入另一个概念——原子引用(AtomicReference)
二、原子引用(AtomicReference)
JUC给造了很多原子类,比如AtomicInteger、AtomicLong等,但是这些只是基本类型的原子类,实际工作中,我们经常要操作的对象是例如User、Order、Customer等对象,这些类JUC是没有办法为我们事先准备的;
那怎么办呢?
就需要使用到原子引用(AtomicReference)——它是一个泛型,可以将普通类,包装成对应的原子类;
-
简单原子引用代码演示:
package com.jiguiquan.www;
import java.util.concurrent.atomic.AtomicReference;
/**
** 演示原子引用
* @author jiguiquan
*
*/
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3",22);
User li4 = new User("li4", 33);
AtomicReference<User> atomicReference = new AtomicReference<User>();
atomicReference.set(z3);
//期望是z3,如果正确,则修改为li4
System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());
}
}
class User{
String name;
int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
执行结果为:

-
代码重现ABA问题
package com.jiguiquan.www;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
** 重现ABA问题,并使用AtomicStampInteger解决ABA问题
* @author jiguiquan
*
*/
public class ABADemo {
//这是普通的原子引用
static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
public static void main(String[] args) {
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
},"t1").start();
new Thread(() -> {
try { //让t2线程sleep一秒钟是为了确保t1线程可以完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 666)+"\t 最新值为:"+atomicReference.get());
},"t2").start();
}
}
执行结果如下:

显然,线程t2根本就不知道线程t1已经执行了一次ABA操作;
-
使用AtomicStampedReference时间戳原子引用解决ABA问题
所谓的AtomicStampedReference,就是指在每一次操作的时候,都增加一次版本号,或者称为时间戳
package com.jiguiquan.www;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
** 重现ABA问题,并使用AtomicStampedInteger解决ABA问题
* @author jiguiquan
*
*/
public class ABADemo {
//这是普通的原子引用
static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
//这是带版本号(时间戳)的原子引用,初始化的时候,除了初始化值外,还要初始化一个版本号
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1);
public static void main(String[] args) {
System.out.println("==============以下是ABA问题的产生===============");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
},"t1").start();
new Thread(() -> {
//让t2线程sleep一秒钟是为了确保t1线程可以完成一次ABA操作
try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100, 666)+"\t 最新值为:"+atomicReference.get());
},"t2").start();
try { TimeUnit.SECONDS.sleep(2); } catch ( InterruptedException e) { e.printStackTrace(); }
System.out.println("============以下是ABA问题的解决办法=============");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+ stamp);
//暂停1秒钟,是为了t4线程可以拿到开始的版本号
try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace(); }
//执行ABA操作
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());
},"t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第一次的版本号:"+stamp);
//这里暂停3秒钟,是为了让t3线程完成一次ABA操作;
try { TimeUnit.SECONDS.sleep(3); } catch ( InterruptedException e) { e.printStackTrace(); }
boolean flag = atomicStampedReference.compareAndSet(100, 666, stamp, atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 修改成功否:"+flag+"\t 此时版本号:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t 当前最新值:"+atomicStampedReference.getReference());
},"t4").start();
}
}
运行结果为:

结论:
经过了t3的ABA操作,在t4执行compareAndSet的时候,即使值为100,符合预期,但是由于版本号不为t4之前拿到的版本号1,此时版本号为3,所以t4的操作无法成功;
ABA问题就这样得到了解决;



