CAS详解
创始人
2024-05-30 08:05:55
0

CAS详解

  • 一 简介
  • 二 CAS底层原理
    • 2.1.AtomicInteger内部的重要参数
    • 2.2.AtomicInteger.getAndIncrement()分析
      • 2.2.1.getAndIncrement()方法分析
      • 2.2.2.举例分析
  • 三 CAS缺点
  • 四 CAS会导致"ABA问题"
    • 4.1.AtomicReference 原⼦引⽤。
    • 4.2.ABA问题的解决(AtomicStampedReference 类似于时间戳)
      • 4.2.1 ABA问题的产生演示
      • 4.2.2.ABA问题的解决

一 简介

CAS的全称为Compare-And-Swap,⽐较并交换,是⼀种很重要的同步思想。它是⼀条CPU并发原语。
它的功能是判断主内存某个位置的值是否为跟期望值⼀样,相同就进⾏修改,否则⼀直重试,直到⼀致为⽌。这个过程是原⼦的。

看下⾯这段代码,思考运⾏结果是

import java.util.concurrent.atomic.AtomicInteger;/*** CAS的全称为Compare-And-Swap,比较并交换,是一种很重要的同步思想。它是一条CPU并发原语。* 它的功能是判断主内存某个位置的值是否为跟期望值一样,相同就进行修改,否则一直重试,直到一致为止。这个过程是原子的。*/
public class CASDemo {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);//CAS操作System.out.println(atomicInteger.compareAndSet(5, 2000) + "\t最终值:" + atomicInteger.get());System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t最终值:" + atomicInteger.get());//true	最终值:2000//false	最终值:2000}
}

分析

第⼀次修改,期望值为5,主内存也为5,修改成功,为2000。
第⼆次修改,期望值为5,主内存为2000,修改失败,需要重新获取主内存的值 。

CAS并发原语体现在JAVA语⾔中就是sum.misc.Unsafe类中的各个⽅法。看⽅法源码,调⽤UnSafe类中的CAS⽅法,JVM会帮我们实现出CAS汇编指令。这是⼀种完全依赖于硬件的功能,通过它实现了原⼦操作。再次强调,由于CAS是⼀种系统原语,原语属于操作系统⽤语范畴,是由若⼲条指令组成的,⽤于完成某个功能的⼀个过程,并且原语的执⾏执⾏是连续的,在执⾏过程中不允许被中断,也就是说 CAS是⼀条CPU的原⼦指令,不会造成所谓的数据不⼀致问题。

二 CAS底层原理

在这里插入图片描述

2.1.AtomicInteger内部的重要参数

  1. Unsafe
    是CAS的核⼼类,由于Java⽅法⽆法直接访问底层系统,需要通过本地(native)⽅法来访问,Unsafe 相当于⼀个后⾯,基于该类可以直接操作特定内存的数据。Unsafe类存在于sum.misc包中,其内部⽅ 法操作可以像C的指针⼀样直接操作内存,因为Java中CAS操作的执⾏依赖于Unsafe类的⽅法。
    注意Unsafe类中的所有⽅法都是native修饰的,也就是说Unsafe类中的⽅法都直接调⽤操作系统底层 资源执⾏相应任务
  2. 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数 据的。
  3. 变量value⽤volatile修饰,保证了多线程之间的内存可⻅性。

2.2.AtomicInteger.getAndIncrement()分析

2.2.1.getAndIncrement()方法分析

AtomicInteger.getAndIncrement()调⽤了 Unsafe.getAndAddInt()⽅法。 Unsafe类的⼤部分 ⽅法都是 native的,⽤来像C语⾔⼀样从底层操作内存。
在这里插入图片描述
C语句代码JNI,对应java⽅法 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5)

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset,jint e, jint x)) 
UnsafeWrapper("Unsafe_CompareAndSwapInt"); 
oop p = JNIHandles::resolve(obj); 
jint* add = (jint *)index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x,addr,e))==e; 
UNSAFE_END 
//先想办法拿到变量value在内存中的地址addr。 
//通过Atomic::cmpxchg实现⽐较替换,其中参数x是即将更新的值,参数e是原内存的值

在这里插入图片描述

这个⽅法的var1和var2,就是根据对象和偏移量得到在主内存的快照值var5。然 后
compareAndSwapInt⽅法通过var1和var2得到当前主内存的实际值。如果这个实际值跟快照值相
等,那么就更新主内存的值为var5+var4。如果不等,那么就⼀直循环,⼀直获取快照,⼀直对⽐,直 到实际值和快照值相等为⽌。

参数介绍
var1 AtomicInteger对象本身
var2 该对象值的引⽤地址
var4 需要变动的数量
var5 是通过var1和var2,根据对象和偏移量得到在主内存的快照值var5

2.2.2.举例分析

⽐如有A、B两个线程,⼀开始都从主内存中拷⻉了原值为3,
A线程执⾏到 var5=this.getIntVolatile,即var5=3。
此时A线程挂起,B修改原值为4,B线程执⾏完毕,由于加了volatile,所以这个修改是⽴即可⻅的。
A线程被唤醒,执⾏ this.compareAndSwapInt()⽅法,发现这个时候主内存的值不等于快照值3,所以继续循环,重新从主内存获取。
在这里插入图片描述

三 CAS缺点

在这里插入图片描述

CAS实际上是⼀种⾃旋锁,

  1. ⼀直循环,开销⽐较⼤。我们可以看到getAndAddInt⽅法执⾏时,有个do while,如果CAS失 败,会⼀直进⾏尝试。如果CAS⻓时间⼀直不成功,可能会给CPU带来很⼤的开销。
  2. 对⼀个共享变量执⾏操作时,我们可以使⽤循环CAS的⽅式来保证原⼦操作,但是,对多个共享变 量操作时,循环CAS就⽆法保证操作的原⼦性,这个时候就可以⽤锁来保证原⼦性。
  3. 引出了ABA问题。

四 CAS会导致"ABA问题"

在这里插入图片描述
所谓ABA问题,就是CAS算法实现需要取出内存中某时刻的数据并在当下时刻⽐较并替换,这⾥存在⼀ 个时间差,那么这个时间差可能带来意想不到的问题。
⽐如,⼀个线程A 从内存位置Value中取出3,这时候另⼀个线程B 也从内存位置Value中取出3,并且线程B进⾏了⼀些操作将值变成了4,然后线程C⼜再次将值变成了3,这时候线程A进⾏CAS操作发现 内存中仍然是3,然后线程A操作成功。
尽管线程A的CAS操作成功,但是不代表这个过程就是没有问题的。在这里插入图片描述

有这样的需求,⽐如CAS,只注重头和尾,只要⾸尾⼀致就接受。
但是有的需求,还看重过程,中间不能发⽣任何修改,这就引出了

4.1.AtomicReference 原⼦引⽤。

AtomicInteger对整数进⾏原⼦操作,如果是⼀个POJO呢?可以⽤ AtomicReference来包装这个 POJO,使其操作原⼦化。

public class AtomicReferenceDemo {public static void main(String[] args) {User user1 = new User("Jack",25);User user2 = new User("Tom",21);User user3 = new User("Ros",28);AtomicReference atomicReference = new AtomicReference<>();atomicReference.set(user1);//CAS操作 主内存中的原始值user1和期望值user1比较相等,返回值为true且将主内存中的原始值修改为user2;System.out.println(atomicReference.compareAndSet(user1,user2)+"\t"+atomicReference.get());//CAS操作 主内存中的原始值user2和期望值user1比较不相等,返回值为false,不更新期望值;System.out.println(atomicReference.compareAndSet(user1,user3)+"\t"+atomicReference.get());//true	User(username=Tom, age=21)//false	User(username=Tom, age=21)}
}

4.2.ABA问题的解决(AtomicStampedReference 类似于时间戳)

在这里插入图片描述
使⽤ AtomicStampedReference类可以解决ABA问题。这个类维护了⼀个“版本号”Stamp,在进⾏CAS 操作的时候,不仅要⽐较当前值,还要⽐较版本号。只有两者都相等,才执⾏更新操作。
解决ABA问题的关键⽅法:
在这里插入图片描述
参数说明:
V expectedReference, 预期值引⽤
V newReference, 新值引⽤
int expectedStamp,预期值时间戳
int newStamp, 新值时间戳

4.2.1 ABA问题的产生演示

public class AtomicReferenceDemo2 {static AtomicReference atomicReference = new AtomicReference<>(100);public static void main(String[] args) {System.out.println("========ABA问题的产生=========");new Thread(() -> {//CAS 主内存中的原始值100和期望值100比较相等,返回值为true且将主内存中的原始值修改为111;atomicReference.compareAndSet(100, 111);//CAS 主内存中的原始值111和期望值111比较相等,返回值为true且将主内存中的原始值修改为100;atomicReference.compareAndSet(111, 100);}, "t1").start();new Thread(() -> {//CASSystem.out.println(atomicReference.compareAndSet(100, 2020) + "\t" + atomicReference.get());}, "t2").start();}

4.2.2.ABA问题的解决

package thread;import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;/*** ABA问题*/
public class ABADemo {//带有时间戳的原子引用 (共享内存值100, 版本号为1)static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {System.out.println("=========ABA问题的解决===========");new Thread(() -> {//获取第一次的版本号int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);//CAS 共享内存值100和期望值100比较相等,且共享内存时间戳和预期值时间戳相等;返回值为true且将共享内存值修改为111时间戳为2;try {//休眠一秒,模拟并发,给ThreadA预留时间启动。TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}atomicStampedReference.compareAndSet(100,111,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());//CAS 共享内存值111和期望值111比较相等,且共享内存时间戳和预期值时间戳相等;返回值为true且将共享内存值修改为100时间戳为3;atomicStampedReference.compareAndSet(111,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());}, "ThreadB").start();new Thread(() -> {//获取第一次的版本号int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);//CAS 休眠3秒,与ThreadB时间差。模拟挂起;让ThreadB先执行,经过线程B的操作当前共享内存值为100,时间戳为3try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}//共享内存值100和期望值100比较相等,但是共享内存时间戳3和预期值时间戳1不相等;返回值为false,不修改共享内存值和时间戳;boolean result = atomicStampedReference.compareAndSet(100,2020,stamp,stamp + 1);System.out.println(Thread.currentThread().getName()+ "\t修改是否成功:" + result+ "\t当前最新的版本号:" + atomicStampedReference.getStamp()+ "\t当前最新的值:" + atomicStampedReference.getReference());}, "ThreadA").start();//=========ABA问题的解决===========//ThreadB	第一次版本号1//ThreadA	第一次版本号1//ThreadB	第二次版本号:2//ThreadB	第三次版本号:3//ThreadA	修改是否成功:false	当前最新的版本号:3	当前最新的值:100}
}

在这里插入图片描述

相关内容

热门资讯

求经典台词和经典旁白 求经典台词和经典旁白谁有霹雳布袋戏里的经典对白和经典旁白啊?朋友,你尝过失去的滋味吗? 很多人在即将...
小王子第二章主要内容概括 小王子第二章主要内容概括小王子第二章主要内容概括小王子第二章主要内容概括
爱情睡醒了第15集里刘小贝和项... 爱情睡醒了第15集里刘小贝和项天骐跳舞时唱的那首歌是什么谢谢开始找舞伴的时候是林俊杰的《背对背拥抱》...
世界是什么?世界是什么概念?可... 世界是什么?世界是什么概念?可以干什么?物质的和意识的 除了我们生活的地方 比方说山 河 公路 ...
全职猎人中小杰和奇牙拿一集被抓 全职猎人中小杰和奇牙拿一集被抓动画片是第五十九集,五十八集被发现,五十九被带回基地,六十逃走
“不周山”意思是什么 “不周山”意思是什么快快快快......一座山,神话里被共工撞倒了。
《揭秘》一元一分15张跑得快群... 一元一分麻将群加群主微【ab120590】【tj525555】 【mj120590】等风也等你。喜欢...
玩家必看手机正规红中麻将群@2... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
始作俑者15张跑的快群@24小... 微信一元麻将群群主微【ab120590】 【tj525555】【mj120590】一元一分群内结算,...
《重大通知》24小时一元红中麻... 加V【ab120590】【tj525555】【mj120590】红中癞子、跑得快,等等,加不上微信就...
盘点一下正规一块红中麻将群@2... 一元一分麻将群加群主微:微【ab120590】 【mj120590】【tj525555】喜欢手机上打...
(免押金)上下分一元一分麻将群... 微【ab120590】 【mj120590】【tj525555】专业麻将群三年房费全网最低,APP苹...
[解读]正规红中麻将跑的快@群... 微信一元麻将群群主微【ab120590】 【tj525555】【mj120590】一元一分群内结算,...
《普及一下》全天24小时红中... 微【ab120590】 【mj120590】【tj525555】专业麻将群三年房费全网最低,APP苹...
优酷视频一元一分正规红中麻将... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
《火爆》加入附近红中麻将群@(... 群主微【ab120590】 【mj120590】【tj525555】免带押进群,群内跑包包赔支持验证...
《字节跳动》哪里有一元一分红中... 1.进群方式-[ab120590]或者《mj120590》【tj525555】--QQ(QQ4434...
全网普及红中癞子麻将群@202... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
「独家解读」一元一分麻将群哪里... 1.进群方式《ab120590》或者《mj120590》《tj525555》--QQ(4434063...
通知24小时不熄火跑的快群@2... 1.进群方式《ab120590》或者《mj120590》《tj525555》--QQ(4434063...