CAS
什么是CAS
CAS其实就是Compare And Swap的简写,乐观锁的代表,它的功能比较当前工作内存中的值和主内存中的值,如果相同则执行指定值的更改(交换),否则继续比较直到主内存和工作内存中的值一致为止。它整个过程是原子的,因为它是一条CPU并发原语。原语的执行必须是连续的,在执行过程中不允许中断,也就是说CAS是一条原子指令,不会造成所谓的数据不—致的问题。CAS在Java中的体现就是Unsafe(魔术类)中的各个方法。
CAS的缺点
① 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,这种自旋比较会给CPU带来很大的开销。
② 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,
但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
③ ABA问题:内存值A被其他线程多次修改,最终又改回了预期值A,但是这种修改再上述规则发现不了
AtomicInteger类底层很多方法都是用CAS,我们以getAndIncrement这个方法为例:

var1 是 Atomiclnteger对象本身。
var2 该对象值得引用地址。
var4 需要变动的数量。
var5 使用var1 var2找出的主内存中真实的值。
用该对象当前的值与var5比较;
如果相同,更新 var5+var4 并且返回true。
如果不同,继续取值然后再比较,直到更新完成。
过程详解
假设线程A和线程B同时执行getAndInt操作(分别跑在不同的CPU上)
1、AtomicInteger里面的value原始值为3,即主内存中Atomiclnteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的副本,分别存储在各自的工作内存
2、线程A通过getIntVolatile(var1 , var2)拿到value值3,这是线程A被挂起(该线程失去CPU执行权)
3、线程B也通过getIntVolatile(var1, var2)方法获取到value值也是3,此时刚好线程B没有被挂起,并执行了compareAndSwapilnt方法,比较内存的值也是3,成功修改内存值为4,线程B打完收工。
4、这是线程A恢复,执行CAS方法,比较发现自己手里的数字3和主内存中的数字4不一致,说明该值已经被其它线程抢先一步修改过了,那么线程A本次修改失败,只能够重新去主内存里读取value值后在来一遍了,也就是在执行do while
5、线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

讲一讲ActomicInteger为什么要用CAS而不用Synchronized?
这里没有用synchronized,而用CAS,这样提高了并发性,也能够实现一致性,是因为每个线程进来后,进入的do while循环,然后不断的获取内存中的值,判断是否为最新,然后在进行更新操作。
AtomicReference
原子引用:AtomicReference(奥套米克瑞佛恩斯)是作用是对 ”对象” 进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。
/**
* @author acoffee
* @create 2022-01-22 17:25
*/
public class AutomicReferenceDemo {
public static void main(String[] args) {
String str1 = "str1";
String str2 = "str2";
AtomicReference<Object> atomicReference = new AtomicReference<>();
atomicReference.set(str1);
System.out.println(atomicReference.compareAndSet(str1,str2)+": "+atomicReference.get().toString());//true: str2
System.out.println(atomicReference.compareAndSet(str1,str2)+": "+atomicReference.get().toString());//false: str2
}
}
ABA问题
引出ABA问题
public class ABADemo {
static AtomicReference atomicReference = new AtomicReference(100);
public static void main(String[] args) {
System.out.println("=========展示ABA问题==========");
new Thread(() -> {
//完成ABA操作
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());// true 2019
},"t2").start();
}
}

ABA问题的解决
可以给它增加一个版本号或者时间戳,JDK给我们提供了AtomicStampedReference 这个类,他就相当于给数据增加的一个标记,就算数据修改过又修改回来,它的标记会变,所以也就不会CAS的条件,会继续从主内存中拿值尝试修改
public class ABASolveDemo {
static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
public static void main(String[] args) {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第一次版本号: "+stamp);
//暂停一秒t1线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//造成ABA问题
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),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());
},"t1").start();
new Thread(() ->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第一次版本号: "+stamp);
//暂停三秒t2线程,保证上面完成了一次ABA操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean flag = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"是否修改成功: "+ flag+"\n当前最新实际版本号: "+atomicStampedReference.getStamp()
+"\n当前实际最新值: "+atomicStampedReference.getReference());
},"t2").start();
}
}



4489

被折叠的 条评论
为什么被折叠?



