JUC并发编程(11)--- 深入理解CAS
Posted 小样5411
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程(11)--- 深入理解CAS相关的知识,希望对你有一定的参考价值。
一、初识CAS
什么是CAS?
CAS就是compareAndSet的缩写,意思是如果我期望(except)的值达到了就更新,否则就不更新,并且CAS是CPU的并发原语!执行效率非常高。
//CAS,compareAndSet:比较并交换
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
运行后,compareAndSet返回的是boolean,成功则可以get(),获取更新后的元素
二、深入理解CAS
我们Ctrl点击compareAndSet,进入底层实现,发现是通过调用unsafe返回实现的,下面讲的compareAndSwapInt()和getAndIncrement()都是unsafe下的方法。
再点击unsafe
我们知道Java无法直接操作内存,但可以通过调用c++来操作内存,unsafe类就相当于java留的后门,可以用来操作内存
我们之前JUC文章说过getAndIncrement方法有别于普通自增方法,底层也是用unsafe实现的,那么我们现在来填坑,简单看看getAndIncrement()自增的效果
//CAS,compareAndSet:比较并交换
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();//自增
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
我们ctrl点击getAndIncrement,再ctrl点击getAndAddInt
CAS底层是比较并交换
原理:CAS(compareAndSet)底层实际上是compareAndSwapInt,就是期望值和var5这个内存中的值比较,如果相同则vat5+var4,即var5+1。这里使用CPU原语,执行效率很高,并且do…while表示自旋锁,意思就是如果不是期望的值,就一直循环,直到是期望值才跳出。
但这样有个缺点,就是可能会出现ABA问题
什么是ABA问题?
举个例子,假如有线程A和线程B,线程A期望atomicInteger=1,而线程B执行先把atomicInteger变为2,后面又变为1。但是线程A获取的时候依然是1,根本不知道线程B把atomicInteger修改过,认为这个atomicInteger一直没有改变过。这就叫ABA问题
//CAS,compareAndSet:比较并交换
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
}
如代码,先由2020变为2021,再由2021变为2020,下面的都不知道被修改过,依然返回true,更新为6666,这样是不行的,我们需要让他就算中间修改过也能知道。
怎么解决呢?
原子引用(AtomicStampedReference)就可以解决这个问题,就是加上一个时间戳Stamp(可理解成版本号),只要被改动过,版本号就会+1,必须版本号和期望值都符合,那么才能更新。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo1 {
public static void main(String[] args) throws InterruptedException {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
System.out.println("************ABA问题的解决*************");
//线程A
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\\t 第一次的版本号" +stamp);
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\\t compareAndSet="+atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println(Thread.currentThread().getName()+"\\t 第二次的版本号" +atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\\t compareAndSet="+atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println(Thread.currentThread().getName()+"\\t 第三次的版本号" +atomicStampedReference.getStamp());
},"A").start();
//线程B
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\\t 第一次的版本号" +stamp);
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(1,4,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\\t 修改是否成功" +result+"\\t A版本号:"+atomicStampedReference.getStamp()+"\\t B版本号"+(stamp+1));
System.out.println(Thread.currentThread().getName()+"\\t 当前最新期望值" +atomicStampedReference.getReference());
},"B").start();
}
}
加一个版本号stamp,必须stamp和期望值都符合,那么才可更改,表示没有发生ABA问题,不然版本号不同,那中间一定被修改过。
以上是关于JUC并发编程(11)--- 深入理解CAS的主要内容,如果未能解决你的问题,请参考以下文章