CAS学习+ABA问题
CAS
定义
比较和交换 (Compare And Swap)它是一条cpu并发原语 是多线程同步的原子指令
作用
判断内存中某个位置的值是否是预期值 如果是就更改为新的值
Unsafe类中getAndAddInt()方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//var1 AtomicInteger对象本身
//var2 该对象的引用地址
//var5 根据var1 var2获取到的主物理内存当中的值
//var4 要变动的数量
我们来模拟一个线程A和线程 B同时执行getAndAddInt()的情况
AtomicInteger里的初始value为3 即就是主物理内存当中的值为3 这是线程A和线程B 同时拷贝了一份value 值3 到了自己的工作内存
var5 = this.getIntVolatile(var1, var2);线程A通过这句代码 拿到了value3 到自己的工作内存
此时线程A被挂起(也就是此时的期望值是3)
线程B也执行 this.getIntVolatile(var1, var2) 这句拿到value 3 同时继续用compareAndSwapInt(var1, var2, var5, var5 + var4)这个CAS方法将3 加一 修改为4 并将4返回到了主物理内存
这是回到线程A A线程执行compareAndSwapInt这个方法比较 此时通过var1和var2拿到的主物理内存的值4已经与3不相同了 那么将不会修改值 将在进入do这一段代码里 拿到最新的主物理内存当中的值 并进行比较直到更新成功
小总结
在我看来 CAS是一种思想
就是先获取主物理内存中的值作为期望值 然后我们再去通过var1 和 var2 获得此时主物理内存的真实值 如果期望值与真实值一致 我们就进行修改 否则 就一直取值比较 直到成功
ABA问题
我们看一下ABA问题的产生
package com.robot;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class ABADemo {
static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
public static void main(String[] args) {
new Thread(()->{
//模拟ABA问题
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"a").start();
new Thread(()->{
//确保线程a先执行发生了aba问题
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100,103));
System.out.println(atomicReference.get());
},"b").start();
}
}
ABA问题的解决
目前在JDK的atomic包里提供了一个类AtomicStampedReference(带版本号的原子引用类)来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值
package com.robot;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo2 {
static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<Integer>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp=atomicStampedReference.getStamp();
//暂停一秒 确保t2拿到第一次的版本号
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"第一次版本号"+stamp);
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第二次版本号"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第三次版本号"+atomicStampedReference.getStamp());
},"t1").start();
new Thread(()->{
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"第一次版本号"+stamp);
//暂停3秒 确保t1执行了ABA问题
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
//此时期望的版本号是1 但是最新的版本号已经是3 所以修改不成功
System.out.println( atomicStampedReference.compareAndSet(100,2020,stamp,10));
System.out.println("当前最新版本号 "+atomicStampedReference.getStamp());
System.out.println("当期最新值"+atomicStampedReference.getReference());
},"t2").start();
}
}