CAS—>UnSafe —> CAS底层思想 —> ABA —> 原子引用更新 —>如何规避ABA问题
如果不理解什么是CAS和UnSafe问题,请跳入Java面试题——CAS(compareAndSet)
通俗点讲就是,就和坐火车时买两头票差不多,线程只管开头和结尾的数据值一样就认为是正确的,中间被谁改过了,它并不知道。
举例说明:现在有两个线程,利用CAS原理来改变某一个共享变量的值,但是A线程在读取到主内存的值为10之后被挂起,B线程开始进行操作,B把主内存中的值改变成了15,之后B线程又把主内存中的值改回10,B线程结束,A线程开始运行,发现内存中的值还是10,认为是没有修改过的,但是实际上已经被B线程修改过。
ABA问题例子:
/**
* @author shihangqi
* @date 2019/10/16 - 20:50
*/
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>();
public static void main(String[] args) {
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t1").start();
}
}
其实就是Atomic类可以对自定义类进行包装。
实例代码:
/**
* @author shihangqi
* @date 2019/10/16 - 16:28
*/
@Getter
@ToString
@AllArgsConstructor
class User{
String Username;
int age;
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3",22);
User li4 = new User("li4",25);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, li4)+"\t "+atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4)+"\t "+atomicReference.get().toString());
}
}
在其中加一种新的机制,就是修改版本号(类似时间戳)
假设A线程从主内存中拿到值为100 版本号为1,B线程也从主内存中拿到了值为100版本号为1,这时A线程挂起,B线程将值修改成了50,但是版本号要跟着修改成2,然后又进行了一次修改将50又改会了100,版本号3,这时B线程结束A线程启动,看到值为100,进行修改得到150但是版本号为2,低于当前版本号3,所以这次操作作废,需要重新去读取主内存中的值。
解决方法的类:使用AtomicStampedReference原子引用
实例代码:
/**
* @author shihangqi
* @date 2019/10/16 - 20:50
*/
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(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(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t2").start();
System.out.println("===========这是ABA问题的解决============");
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println((Thread.currentThread().getName()+"\t第一次版本号:"+stamp));
//暂停1秒钟t3线程
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e){
e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第3次版本号:"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println((Thread.currentThread().getName()+"\t第一次版本号:"+stamp));
//验证上面的t3线程完成了一次ABA操作
try {
TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e){
e.printStackTrace(); }
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t修改成功否:"+result+"\t当前最新实际版本号为:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前实际最新值:"+atomicStampedReference.getReference());
},"t4").start();
}
}