首页 > 其他分享 >CAS自旋

CAS自旋

时间:2022-11-11 16:56:36浏览次数:31  
标签:compare exchange CAS value dest 自旋 jint

 

1、CAS是什么?

比较并交换(compare and swap, CAS),是原子操作的一种。在多线程没有锁的状态下,可以保证多个线程对同一个值的更新

通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。

CAS的伪代码的逻辑

if (value == expectedValue) {
    value = newValue;
}
CAS 可以看作是它们合并后的整体一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。

在多线程场景下,不加锁的情况下来修改值,CAS是怎么自旋的呢

 以上图举例说明:

1、现在Data中存放的是N=0,线程A将N=0拷贝到自己的工作内存中,即E等于N的值,E=0,做加1操作,V=E+1=1

2、由于是在多线程不加锁的场景下操作,所以可能此时N被别的线程修改为其他值,此时需要再次读取N看其是否被修改

3、如果被修改,即E != N,说明被其他线程修改过,那么此时工作内存中的E已经和主存中的N不一致了,需要重新读取N的值拷贝到工作内存E中,直到E = N才能修改

4、如果没被修改,即E= N ,说明没被其他线程修改过,那么将工作内存中的E=0改为E=1,同时写回主存,将N=0改为N=1。

 

2、ABA问题

1、什么是ABA问题?

在线程A计算V的时候,可能线程B将N=0改为了N=2,线程C又将N=2改为了N=0,此时的N值虽然还是0,还是N已经不是一开始的N了

2、如何解决ABA问题?

1、添加版本号,我们把值N加一个版本号tag,当有线程修改的时候,版本号就会发生变化。在读取E的时候,同时将版本号tag也读取上,在比较E == N的时候,同时比较tag是否发生了改变

实际应用此方案的有:AtomicStampedReference类

2、添加时间戳也可以解决。查询的时候把时间戳一起查出来,对的上才修改并且更新值的时候一起修改更新时间,这样也能保证,方法很多但是跟添加版本号都是异曲同工之妙。

 

3、基于CAS的应用

基于CAS的应用有 信号量Semaphore、juc原子类

juc原子类:

基本类型 AtomicInteger、AtomicLong、AtomicBoolean
引用类型 AtomicReference、AtomicStampedRerence、AtomicMarkableReference
数组类型 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
对象属性原子修改器 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
原子类型累加器(jdk1.8增加的类) DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64

 

4、CAS源码分析

CAS的应用:Unsafe类
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);
public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update)

可以看到是native修饰的方法,native说明是通过jni调用C++的代码了,我们就来看一下C++中是怎么实现的CAS。下面是unsafe.cpp中代码实现

#unsafe.cpp
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);
  // 根据偏移量,计算value的地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  // Atomic::cmpxchg(x, addr, e) cas逻辑 x:要交换的值   e:要比较的值
  //cas成功,返回期望值e,等于e,此方法返回true 
  //cas失败,返回内存中的value值,不等于e,此方法返回false
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

核心逻辑在Atomic::cmpxchg方法中,这个根据不同操作系统和不同CPU会有不同的实现。这里我们以linux_64x的为例,查看Atomic::cmpxchg的实现

#atomic_linux_x86.inline.hpp
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  //判断当前执行环境是否为多处理器环境
  int mp = os::is_MP();
  //LOCK_IF_MP(%4) 在多处理器环境下,为 cmpxchgl 指令添加 lock 前缀,以达到内存屏障的效果
  //cmpxchgl 指令是包含在 x86 架构及 IA-64 架构中的一个原子条件指令,
  //它会首先比较 dest 指针指向的内存值是否和 compare_value 的值相等,
  //如果相等,则双向交换 dest 与 exchange_value,否则就单方面地将 dest 指向的内存值交给exchange_value。
  //这条指令完成了整个 CAS 操作,因此它也被称为 CAS 指令。
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;

cmpxchgl的详细执行过程:

  输入参数是"r" (exchange_value), “a” (compare_value), “r” (dest), “r” (mp),参数前面的r表示通用寄存器,a表示eax寄存器,即compare_value存入eax寄存器,而exchange_value、dest、mp的值存入任意的通用寄存器。嵌入式汇编规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以”0%”开始,分别记为%0,%1....%9。也就是说,输出的eax是%0,输入的exchange_value、compare_value、dest、mp分别是%1,%2,%3,%4。

  因此cmpxchgl %1,(%3)实际上表示的是cmpxchgl exchange_value,(dest) ,需要注意的是cmpxchgl 有个隐含操作数,其实际过程是先比较eax(即compare_value)的值和dest地址所存的值是否相等。输出参数"=a" (exchange_value) 表示把eax中存的值写入eachange_value变量中。

  如果cmpxchgl执行时compare_value和dest指针指向内存的值相等,则会使得dest指针指向内存的值变成exchange_value,最终eax的campare_value赋值给exchange_value变量,即函数最终返回值是原先的compare_value,此时Unsafe_CompareAndSwapInt函数的返回值(jint)(Atomic::cmpxchg(x, addr, e)) == e就是true,表示CAS成功,

  如果cmpxchgl执行时compare_value和dest不相等,则会将当前dest指针指向内存的值写入eax,最终输出时赋值给exchange_value变量作为返回值,导致(jint)(Atomic::cmpxchg(x, addr, e)) == e结果为false,表示CAS失败

 

LOCK_IF_MP(%4)方法内部为

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

LOCK_IF_MP(mp)函数中的入参mp全称是Multi Processor ,多CPU。

注意这个函数里有个lock; 1,意思是什么呢,就是看你操作系统有多少个处理器,如果只有一个cpu一核的话就不需要原子性了,一定是顺序执行的,如果是多核心多cpu前面就要加lock。

所以最终实现CAS的汇编指令是lock cmpxchgl 指令

 

 

 

 

标签:compare,exchange,CAS,value,dest,自旋,jint
From: https://www.cnblogs.com/kiko2014551511/p/16880798.html

相关文章

  • threejs raycaster 鼠标事件的监听
    91行,传入scene.childern,那么就是监听整个场景中所有的childern其实也可传入一个特定的物体的孩子,这样就会监听这个物体上的鼠标事件......
  • Open Cascade 多视图-多个3D视图
    ​1.简介本文介绍OCC如何实现多窗口视图以及单个窗口的多视图功能。OpenCascade7.7.0Beta中引入一个新功能:新增口来创建视图的子视图,改进对多视图的支持,以此达到在......
  • Open Cascade 获取面的内外环线
    ​1.简介在特定应用场景下,需要对于一个拓扑面(TopoDS_Face)其进行补洞或打洞操作,如下图所示。补洞或打洞过程中需要获取面的环线(TopoDS_Wire),本文即介绍如何获取拓扑面的环......
  • k8s03-Replication-Controller-ReplicaSet-有状态-无状态-daemonset
    ReplicationController和ReplicaSet无状态应用管理Deployment有状态应用管理StatefuSet守护进程集DaemonSetkubernets调度基础1.1RC和RSReplicationController......
  • java.sql.SQLException: java.lang.ClassCastException:java.math.BigInteger cannot
    错误原因:由于在IDEA中导入的驱动包的版本和数据库的版本不匹配解决方案:​​数据库官网中下载与数据库配套的Jar包,重新导入就可以了​官网:​​https://dev.mysql.com/downloa......
  • 大话CAS
    1.无锁的概念在谈论无锁概念时,总会关联起乐观派与悲观派,对于乐观派而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事,但对于悲观......
  • 判断语句Select Case,比If-Else语句的整洁,容易看懂的他,你只需1分钟就学会
    Hi,大家好,本专栏将会从零开始和大家用图文的方式,让你从零基础学会VBA!有兴趣的小伙伴可以持续关注我,或者在专栏进行查看学习,愿与君携手共进!在上一章节我们已经学习过如何使用I......
  • CAS无锁机制
    1、背景传统Synchronized锁:悲观,如果没有获取到锁的情况下,会让当前线程变为阻塞的状态,释放CPU执行权,效率非常低。乐观锁:本质上没有锁,没有死锁现象,而且效率比较高......
  • CF460C Present & CF954G Castle Defense
    没错是双倍经验。题意:一个长度为\(n\)的序列\(a\),你有\(m\)次操作的机会,每次操作是将其中连续的\(w\)个元素增加\(1\)。最大化最终序列的最小值。\(1\leqw......
  • oracle case when 用法总结
    ​​Oracledbms_jobpackage用法小结​​ORACLECASEWHEN及SELECTCASEWHEN的用法  Case具有两种格式。简单Case函数和Case搜索函数。--简单case函数casesex......