CAS的意思是campare and sweep比较交换

这个如果不用代码会比较抽象,那么在源码中进行解释

void ATTR ObjectMonitor::enter(TRAPS) {
// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
Thread * const Self = THREAD ;
void * cur ; cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
} if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
}

这个就是cas:

  • 参数1:期望更新值
  • 参数2:内存地址,希望更改地址中的值
  • 参数3,预料内存地址中的旧址
  • 返回结果: 返回内存地址中的值,
    • 第一步,将参数1的值放入到rax中,
    • 如果cas更新成功,如果相等,则双向交换 dest 与 exchange_value  返回rax,此时rax为参数1
    • 如果cas失败,否则就单方面地将 dest 指向的内存值交给 ``exchange_value,返回rax,此时rax为参数3

比如以前程序

可以参考:https://www.cnblogs.com/yonghengzh/p/14277544.html

那么通过代码验证

参数1

(gdb) p Self
$6 = (JavaThread * const) 0x7f1e0000b800

参数2

(gdb) p this
$4 = (ObjectMonitor * const) 0x7f1de8002650
(gdb) p * this
$5 = {
static SpinCallbackFunction = 0x0,
static SpinCallbackArgument = 0,
_header = 0x1,
_object = 0xf38068d0,
SharingPad = {-7.4786357953083842e+240},
_owner = 0x0,
_previous_owner_tid = 0,
_recursions = 0,
OwnerIsThread = 1,
_cxq = 0x0,
_EntryList = 0x0,
_succ = 0x0,
_Responsible = 0x0,
_PromptDrain = -235802127,
_Spinner = -235802127,
_SpinFreq = 0,
_SpinClock = 0,
_SpinDuration = 4400,
_SpinState = -1012762419733073423,
_count = 0,
_waiters = 1,
_WaitSet = 0x7f1df0dfabb0,
_WaitSetLock = 0,
_QMix = -235802127,
FreeNext = 0x0,
StatA = -1012762419733073423,
StatsB = -1012762419733073423,
static _sync_ContendedLockAttempts = 0x7f1e0000d928,
static _sync_FutileWakeups = 0x7f1e0000d9f8,
static _sync_Parks = 0x7f1e0000dab8,
static _sync_EmptyNotifications = 0x7f1e0000db78,
static _sync_Notifications = 0x7f1e0000dc38,
static _sync_SlowEnter = 0x7f1e0000dcf8,
static _sync_SlowExit = 0x7f1e0000ddb8,
static _sync_SlowNotify = 0x7f1e0000de78,
static _sync_SlowNotifyAll = 0x7f1e0000df38,
static _sync_FailedSpins = 0x0,
static _sync_SuccessfulSpins = 0x7f1e0000e0b8,
static _sync_PrivateA = 0x7f1e0000e178,
static _sync_PrivateB = 0x7f1e0000e238,
static _sync_MonInCirculation = 0x7f1e0000e2f8,
static _sync_MonScavenged = 0x7f1e0000e3b8,
static _sync_Inflations = 0x7f1e0000d3a8,
static _sync_Deflations = 0x7f1e0000d868,
static _sync_MonExtant = 0x7f1e0000e478,
static Knob_Verbose = 0,
static Knob_SpinLimit = 5000
}

参数3

NULL 为0x0

那么分析如下,如果cas成功的话,预期,&_owner 即参数2的取值为 变为参数1,且返回值为变更后的值 即新值Self

      如果cas失败,预期 参数2的取值不变,返回值为参数2中的取值

inline void*    Atomic::cmpxchg_ptr(void*    exchange_value, volatile void*     dest, void*    compare_value) {
return (void*)cmpxchg((jlong)exchange_value, (volatile jlong*)dest, (jlong)compare_value);
} ==============
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
bool mp = os::is_MP();
__asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
: "=a" (exchange_value) //输出约束条件
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)//输入约束条件
: "cc", "memory");
return exchange_value;
}

解析内敛汇编

清单 C:内联汇编代码块的组成

__asm__ __volatile__(assembly template

        : output operand list

        : input operand list

        : clobber list

                            );

2. 汇编模板:

  • = 表示该变量是只写的。& 表示这个变量不能与任何输入操作数共享相同的寄存器
  • 字母a是寄存器EAX/AX/AL的缩写,说明cr0的值要从寄存器EAX中获取,也就是说cr0=eax,最终这一点被转化成汇编指令就是:movl %eax,address_of_cr0;
  • 常用的寄存器约束的缩写:
    r:I/O,表示使用一个通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中选取一个GCC认为是合适的;
    q:I/O,表示使用一个通用寄存器,与r的意义相同;
    g:I/O,表示使用寄存器或内存地址;
    m:I/O,表示使用内存地址;
    a:I/O,表示使用%eax/%ax/%al;
    b:I/O,表示使用%ebx/%bx/%bl;
    c:I/O,表示使用%ecx/%cx/%cl;
    d:I/O,表示使用%edx/%dx/%dl;
    D:I/O,表示使用%edi/%di;
    S:I/O,表示使用%esi/%si;
    f:I/O,表示使用浮点寄存器;
    t:I/O,表示使用第一个浮点寄存器;
    u:I/O,表示使用第二个浮点寄存器;
    A:I/O,表示把%eax与%edx组合成一个64位的整数值;
    o:I/O,表示使用一个内存位置的偏移量;
    V:I/O,表示仅仅使用一个直接内存位置;
    i:I/O,表示使用一个整数类型的立即数;
    n:I/O,表示使用一个带有已知整数值的立即数;
    F:I/O,表示使用一个浮点类型的立即数;
    2.内存约束:
    如果一个Input/Output操作表达式的C/C++表达式表现为一个内存地址(指针变量),不想借助于任何寄存器,则可以使用内存约束;比如:
    __asm__("lidt %0":"=m"(__idt_addr));或__asm__("lidt %0"::"m"(__idt_addr));
    内存约束使用约束名"m",表示的是使用系统支持的任何一种内存方式,不需要借助于寄存器;
    使用内存约束方式进行输入输出时,由于不借助于寄存器,所以,GCC不会按照你的声明对其做任何的输入输出处理;GCC只会直接拿来使用,对这个C/C++表达式而言,究竟是输入还是输出,完全依赖于你写在"instruction list"中的指令对其操作的方式;所以,不管你把操作约束和操作表达式放在Input部分还是放在Output部分,GCC编译生成的汇编代码都是一样的,程序的执行结果也都是正确的;本来我们将一个操作表达式放在Input或Output部分是希望GCC能为我们自动通过寄存器将表达式的值输入或输出;既然对于内存约束类型的操作表达式来说,GCC不会为它做任何事情,那么放在哪里就无所谓了;但是从程序员的角度来看,为了增强代码的可读性,最好能够把它放在符合实际情况的地方;
    3.立即数约束:
    如果一个Input/Output操作表达式的C/C++表达式是一个数字常数,不想借助于任何寄存器或内存,则可以使用立即数约束;
    由于立即数在C/C++表达式中只能作为右值使用,所以,对于使用立即数约束的表达式而言,只能放在Input部分;比如:
    __asm__ __volatile__("movl %0,%%eax"::"i"(100));
    立即数约束使用约束名"i"表示输入表达式是一个整数类型的立即数,不需要借助于任何寄存器,只能用于Input部分;使用约束名"F"表示输入表达式是一个浮点数类型的立即数,不需要借助于任何寄存器,只能用于Input部分;
    4.通用约束:
    约束名"g"可以用于输入和输出,表示可以使用通用寄存器、内存、立即数等任何一种处理方式;
    约束名"0,1,2,3,4,5,6,7,8,9"只能用于输入,表示与第n个操作表达式使用相同的寄存器/内存;
    通用约束"g"是一个非常灵活的约束,当程序员认为一个C/C++表达式在实际操作中,无论使用寄存器方式、内存方式还是立即数方式都无所谓时,或者程序员想实现一个灵活的模板,以让GCC可以根据不同的C/C++表达式生成不同的访问方式时,就可以使用通用约束g;
    例如:
    #define JUST_MOV(foo) __asm__("movl %0,%%eax"::"g"(foo))
    则,JUST_MOV(100)和JUST_MOV(var)就会让编译器产生不同的汇编代码;
    对于JUST_MOV(100)的汇编代码为:
    #APP
     movl $100,%eax      #立即数方式;
    #NO_APP
    对于JUST_MOV(var)的汇编代码为:
    #APP
     movl 8(%ebp),%eax   #内存方式;
    #NO_APP
    像这样的效果,就是通用约束g的作用;
    5.修饰符:
    等号(=)和加号(+)作为修饰符,只能用于Output部分;等号(=)表示当前输出表达式的属性为只写,加号(+)表示当前输出表达式的属性为可读可写;这两个修饰符用于约束对输出表达式的操作,它们俩被写在输出表达式的约束部分中,并且只能写在第一个字符的位置;
    符号&也写在输出表达式的约束部分,用于约束寄存器的分配,但是只能写在约束部分的第二个字符的位置上;

    四、占位符
    每一个占位符对应一个Input/Output操作表达式;
    带C/C++表达式的内联汇编中有两种占位符:序号占位符和名称占位符;
    1.序号占位符:
    GCC规定:一个内联汇编语句中最多只能有10个Input/Output操作表达式,这些操作表达式按照他们被列出来的顺序依次赋予编号0到9;对于占位符中的数字而言,与这些编号是对应的;比如:占位符%0对应编号为0的操作表达式,占位符%1对应编号为1的操作表达式,依次类推;
    由于占位符前面要有一个百分号%,为了去边占位符与寄存器,GCC规定:在带有C/C++表达式的内联汇编语句的指令列表里列出的寄存器名称前面必须使用两个百分号(%%),一区别于占位符语法;
    GCC对占位符进行编译的时候,会将每一个占位符替换为对应的Input/Output操作表达式所指定的寄存器/内存/立即数;
    例如:
    __asm__("addl %1,%0\n\t":"=a"(__out):"m"(__in1),"a"(__in2));
    这个语句中,%0对应Output操作表达式"=a"(__out),而"=a"(__out)指定的寄存器是%eax,所以,占位符%0被替换为%eax;占位符%1对应Input操作表达式"m"(__in1),而"m"(__in1)被指定为内存,所以,占位符%1被替换位__in1的内存地址;
    用一句话描述:序号占位符就是前面描述的%0、%1、%2、%3、%4、%5、%6、%7、%8、%9;其中,每一个占位符对应一个Input/Output的C/C++表达式;

完成

分析

inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
bool mp = os::is_MP();
__asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"   //占位符, cmpxchgq exchange_value 第2二表达式 ,(dest)第四个表达式
: "=a" (exchange_value)//输出约束,:movl %eax,address_of_exchange_value;
: "r" (exchange_value), //将(内的值)放入通用寄存器     "a" (compare_value), //将(括号内的值)放入rax寄存器    "r" (dest), // 将(dest)放入通用寄存器   "r" (mp) //放入通用寄存器
: "cc", "memory");
return exchange_value;  返回
}

2.2 cmpxchgq
cmpxchg是汇编指令
作用:比较并交换操作数.
如:CMPXCHG r/m,r 将累加器AL/AX/EAX/RAX中的值与首操作数(目的操作数)比较,如果相等,第2操作数(源操作数)的值装载到首操作数,zf置1。如果不等,首操作数的值装载到AL/AX/EAX/RAX并将zf清0

一般cmpxchg有两种存在形式
cmpxchg bx, cx:
如果AX与BX相等,则CX送BX且ZF置1;否则BX送AX,且ZF清0
换成AT&T的格式就是
由于ATT汇编与Intel不同,操作数2,和操作数1互换。
cmpxchg %cx, %bx

那么分析 cmpxchgq exchange_value 第2二表达式 ,(dest)第四个表达式

  • 如果ax 与bx相等,则cx 双向交换 bx,
  • 否则,否则就单方面地将 dest 指向的内存值交给ax
    • 第一步,将参数1的值放入到rax中,
    • 如果cas更新成功,如果相等,则双向交换 dest 与 exchange_value  返回rax,此时rax为参数1
    • 如果cas失败,否则就单方面地将 dest 指向的内存值交给 ``exchange_value,返回rax,此时rax为参数3

那么本条就是

========================================================================================================================

第一步:rax中的值设置为exchange_value  

第二步:判断rax中的值比较 dest 指针指向的内存值是否相等,如果相等,则双向交换dest 与 exchange_value,

    此时的dest指针中的内存值为新值,  "cmpxchgq %1,(%3)" ,那么 %1中的值就变为了原始内存值(亦为预期值),这个%1寄存器也是%rax寄存器,

    所以此时成功后rax寄存器中的值为参数3

第三步:如果二者不相等,那么将

 dest 指向的内存值交给 ``exchange_value,即将参数2的值(bx)的值给rax,那么此时rax的值为参数2

===========================================================================================================================

如果 ax 与 bx(dest)相等,则cx(exchange_value )与bx进行双向交互,那么此时ax中为exchange_value 即期望更新的值

否则 bx(dest)送ax

整个程序返回rax

结合实际分析一下源码

将要执行cas操作,

第一步,将rax中的值设置为Self

预期内存地址中的值为0x0,如果成功_owener的值为Self, %1 (%3)交换的第一部分,将%1(rax)的值变为0x0,所以返回值为0x0,接下来判断cur==NULL代表cas成功

如果失败,则将owner内存值给rax,那么rax中就是其他线程的值

那么此时

jvm源码解读--16 cas 用法解析的更多相关文章

  1. jvm源码解读--06 Method 方法解析

    进入 // Methods bool has_final_method = false; AccessFlags promoted_flags; promoted_flags.set_flags(0) ...

  2. jvm源码解读--16 锁_开头

    现在不太清楚, public static void main(String[] args) { Object object=new Object(); System.out.println(&quo ...

  3. JVM 源码解读之 CMS 何时会进行 Full GC

    t点击上方"涤生的博客",关注我 转载请注明原创出处,谢谢!如果读完觉得有收获的话,欢迎点赞加关注. 前言 本文内容是基于 JDK 8 在文章 JVM 源码解读之 CMS GC 触 ...

  4. jvm源码解读--17 Java的wait()、notify()学习

    write and debug by 张艳涛 wait()和notify()的通常用法 A线程取得锁,执行wait(),释放锁; B线程取得锁,完成业务后执行notify(),再释放锁; B线程释放锁 ...

  5. jvm源码解读--08 创建oop对象,将static静态变量放置在oop的96 offset处

    之前分析的已经加载的.Class文件中都没有Static 静态变量,所以也就没这部分的解析,自己也是不懂hotspot 将静态变量放哪里去了,追踪源码之后,看清楚了整个套路,总体上来说,可以举例来说对 ...

  6. jvm源码解读--11 ldc指令的解读

    写一个java文件 public static void main(String[] args) { String str1="abc"; String str2 ="a ...

  7. Golang 源码解读 01、深入解析 strings.Builder、strings.Join

    strings.Builder 源码解析. 存在意义. 实现原理. 常用方法. 写入方法. 扩容方法. String() 方法. 禁止复制. 线程不安全. io.Writer 接口. 代码. stri ...

  8. jvm源码解读--13 gc_root中的栈中oop的mark 和copy 过程分析

    粘贴源码 package com.test; import java.util.Random; public class Test { static int number=12; private in ...

  9. jvm源码解读--10 enum WKID 枚举

    源码中对于枚举类型WKID的使用 static bool initialize_wk_klass(WKID id, int init_opt, TRAPS); static void initiali ...

随机推荐

  1. 腾讯TencentOS 十年云原生的迭代演进之路

    导语 TencentOS Server (又名 Tencent Linux 简称 Tlinux) 是腾讯针对云的场景研发的 Linux 操作系统,提供了专门的功能特性和性能优化,为云服务器实例中的应用 ...

  2. Java中JVM、JRE和JDK三者有什么区别和联系?

    Java 语言的开发运行,也离不开 Java 语言的运行环境 JRE.没有 JRE 的支持,Java 语言便无法运行.当然,如果还想编译 Java 程序,搞搞小开发的话,JRE 是明显不够了,这时候就 ...

  3. 一次SQL查询优化原理分析(900W+数据,从17s到300ms)

    有一张财务流水表,未分库分表,目前的数据量为9555695,分页查询使用到了limit,优化之前的查询耗时16 s 938 ms (execution: 16 s 831 ms, fetching: ...

  4. 一文带你了解 Redis 的发布与订阅的底层原理

    01.前言 发布订阅系统在我们日常的工作中经常会使用到,这种场景大部分情况我们都是使用消息队列的,常用的消息队列有 Kafka,RocketMQ,RabbitMQ,每一种消息队列都有其特性,关于 Ka ...

  5. csp-s模拟测试52-53

    留坑.... 改完题再说吧. 留坑....最近考得什么鬼??模拟53T1 u(差分) 一道差分题????然而考场没有想到如何维护斜率上的差分,事后经miemeng和cyf的生(xuan)动(xue)讲 ...

  6. 关于equals()和hashcode()的一些约定

    本文章主要讨论和回答一下几个问题: equals()的四大特性 equals()和hashcode()之间的关系,为什么我们经常说这两个方法要么都重写,要么都不重写? HashMap.HashSet等 ...

  7. Mongo开启用户认证

      1. 介绍 由于mongodb默认没有设置密码访问,而且mongodb的访问权限设计,必须使用有权限的用户给每个库设置一个用户,才能使用,且2.X版本与3.X版本区别有点大,所以要注意以下几点. ...

  8. 12-1 MySQL数据库备份(分库)

    #!/bin/bash source /etc/profile DATE="$(date +%F_%H-%M-%S)" DB_IP="172.16.1.122" ...

  9. CentOS-搭建MinIO集群

    一.基础环境 操作系统:CentOS 7.x Minio在线演示 Minio下载 二.准备工作 2.1.机器资源 192.168.1.101 /data1 192.168.1.102 /data2 1 ...

  10. javascript数组 (转)

      javascript的Array可以包含任意数据类型,并通过索引来访问每个元素.   要取得Array的长度,直接访问length属性:   var arr = [1,2,3.14,'Hell0' ...