AtomicInteger源码解析,CAS底层分析,JNI调用细节

上篇博文地址:https://www.jianshu.com/p/15f17da87ba6

上回书我们说到,借助volatile+AtomicInteger完成了对于int数据的数据同步问题。
我们了解到volatile只能保证内存可见性,不能保证原子性,通过AtomicInteger完成原子性操作。
而AtomicInteger的实现时借助CAS算法实现的。

CAS底层实现原理是什么?

为了更好的描述这个问题,我们详细查看AtomicInteger类去实现,这里我们通过分析
AtomicInteger保证上个例子中的原子性的,就能大概了解CAS算法的实现思路了。

探究为什么这里会出现数据错误?

注意:其实 ++ 操作是不具备原子性的,下面我们通过图例的方式描述一下 ++ 操作是如何执行的:
测试++不具备原子操作
这里我们发现其实++操作本身是分为三个步骤的,所以在多线程情景下中间步骤就有可能被打断,导致
5个线程累加1000次时,其实永远<=5000。
线程A读取主内存值N,读取到A线程的工作内存中,接下来在A线程的工作内存中+1;
当累加完之后准备写回到主内存中,但是还没有写时,B线程也读区到了主内存中的值,而此时读到
的值也是N,相当于A,B两个线程都做了++操作,但是只类加了1。

查看源码以及JNI如何保证同步的

这里需要一些c的基础以及一些汇编的基础,但是涉及不深入,我尽量表述的让大家都能看懂。

源码解析

环境准备:

我工具采用IDEA,jdk版本是9,源码导入也是1.9.
为了查看底层JNI(java native interface java本地接口),下载了openjdk,版本是8.
虽然版本不对应,但是思路是一样的。
openjdk下载地址:http://jdk.java.net/

源码:

我这里截取的是整个调用过程最重要的一些源码,方便大家查看:

Test02调用代码:

测试用例:代码分析
这里有三个调用需要注意:
1、创建AddCound对象(对象的创建导致AtomicInteger对象也创建了)
2、线程执行了,导致AddCound.run()执行了,此时会调用getAddIncrement();
3、最后在主线程中通过get();方法获取值。
接下来我们看看上述三步哪些哪些代码会执行呢?

AtomicInteger源码分析:

这里为了方便,我将最重要的代码分析如下:
测试用例:AtomicInteger源码解析
这里基本没有一行代码是不重要的,当然这些源码也比较简单。
1、由于Test02对于AddCount的创建,导致AtomicInteger被创建,这里创建之后让我们的Unsafe
也创建出来了,由于AtomicInteger的创建是通过空构造器创建的,所以属性value的值是0。
2、注意value属性是通过volatile修饰的,所以底层是无法对于value变量进行重排序的。
3、VALUE的值是通过反射获取属性value的值进行赋值的,但是这里的VALUE的值其实是一个偏移量
   这个偏移量我理解的是:当前线程对象中value属性在对象中的偏移量,为了方便获取value的值。
4、get();方法是获取属性value的值
5、getAndIncrement()方法调用了Unsafe中国呢的getAndInt()方法:
    说明一下getAndInt方法的几个参数:
    a、Object o:代表是对那个对象/进行操作
    b、long offset:代表偏移量,
    c、int delta:每次递增的数
    d、返回值 int:前一个值,因为这里个方法模拟的是后加

Unsafe源码分析:

测试用例:Unsafe源码解析
这里已经就开始我们的重头戏:
1、getAndAddInt();方法:
    a、先获取一个v值,这个v就是CAS算法中的预估值;
    b、通过CAS-Loop ,不断重试,直到成功。
    这里我们可以大概稍微了解为什么说CAS是非阻塞算法了,因为我们在这里并没有看到锁。
    这里所有线程都可以不断的Loop,通过cpu提供的特殊指令自动更新共享数据。
    至于怎么做到的,我们继续。
2、weakCompareAndSetInt()方法参数说明:通过这个方法在比较和替换。
        1、Object o:代表是对那个对象/进行操作
        2、long offset:代表偏移量 通过偏移量是可以获取到内存值:V
        3、int expected:预估值 A
        4、int x: 修改值:B

这里我们其实可以大胆猜测一下JNI实现的机制无非就是取V值和A值进行判定,如果相等,那么将更新值赋给内存值V=B,如果不想等则不进行赋值操作。

但是这里的问题是,java如何通过偏移量获取内存值的呢?其实上文已经说过了,对象在创建时其实是已经
知道空间大小的,而VALUE其实就是对象中的属性value对于对象而言的偏移量,那么直接可以通过对象的地址相对定位获取到当前value的值。这块的内容推荐大家可以详细阅读揭秘Java虚拟机 前50页

简单流程总结一波:

测试用例:流程图

查看JNI中Unsafe的实现:

这里Unsafe的实现在openjdk\hotspot\src\share\vm\prims\unsafe.cpp文件中
简单说一下如何阅读.cpp文件,掌握精髓,连蒙带猜:一下内容:
JNI实现
测试用例:UnsafeJNI实现
在这里我通过图例的方式已经将大概的如何读JNI内容的方式写出来了;
方法的返回值、名称调用,参数类型其实和之前大同小异,多找一些方法看看基本就能猜个大概。
我们在这里大概看看:调用了Atomic中的cmpxchg方法,传入的参数分别是:
    a、x:修改值 B
    b、addr:内存值 V
    c、e:预估值 A

查看JIN中Atomic的实现:

    这里Atomic的实现在openjdk\hotspot\src\os_cpu\windows_x86\vm
    \atomic_windows_x86.inline.hpp
    这里我直接找到inline.hpp;其实应该先找atomic.hpp,在这里其实没有太大关系:
测试用例:AtomicJNI实现
1、这个汇编的意思是isMultiprocessor,是否多核处理器,只有多处理器下才会出现问题,单核不
会出现数据不一致问题的。
2、_asm:代表汇编开始
3、LOCK_IF_MP()是一个函数,根据mp如果是多核处理器,则会添加一个lock前缀(其实这个就是
传说中的内存屏障,至于有什么用,如何实现我们后续再聊)。
4、cmpxchg是一个汇编指令,比较并交换操作数:
简单解释cmpxchg指令:
写法:cmpxchg r/m 
r是第一个操作数,m为第二个操作数 
cmpxchg会将eax/rax等寄存器中的数和第一个操作数比较,如果相等,则会将第二个操作数的值存放
到第一个操作数中,如果不相等,会将第一个操作数的值存放到eax/rax等寄存器中。

汇编中的一些值操作简单介绍

mov edx,dest --> 将dest 也就是内存值V的值放到寄存器edx中;

mov ecx,exchange_value --> 将exchange_value 也就是修改值B的值放到寄存器ecx中;

mov eax,compare_value --> 将compare_value 也就是预估值A的值放到寄存器eax中;

图示Atomic中的CAS实现

测试用例:图示AtomicCAS实现

结论:

这里的汇编我们不在深入,我们大概只要知道,如果是多核处理器,那么会在cmpxchg上加一个lock前缀,然后会将eax寄存器也就是A的值和edx寄存器也就是V的值进行比较,如果相等,edx中存放ecx的值,也就是在edx寄存器中存入的是修改之后的值。

至此,我们的CAS揭秘就告一段落了,但是有出现了新的问题,为什么说CAS是非阻塞的?

Lock的作用:

  • 在执行cmpxchg期间,锁住内存地址[edx]的地址,其他处理器不能访问,保证原子性
  • 将本处理器写缓存全部强制写回到主存中,实现写屏障,保证每个线程的工作内存数据于主存一致
  • 禁止cmpxchg 的前后重排序

CAS操作和volatile作用是一致的,可以保证数据可见性,不一样的地方在于底层通过JNI声明#Lock信号,保证数据原子性

扩展知识 cpu锁的实现

总线锁

多处理器访问内存数据,某个处理器在使用某个共享数据时,当前处理器增加一个#lock信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存

缓存锁

使用总线锁,导致开销特别大,使用缓存锁(缓存锁定):就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言#LOCK信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效。

CAS有哪些问题?如何解决?

  • 只能保证一个变量的原子操作,如果有多个变量这个时候cas就有点无能为力了。

解决方案1:

通过加锁的方式解决,但是效率就会降低

解决方案2:

通过java.util.concurrent.atomic.AtomicReference构建一个基于atomic原子的对象引用出来,将多个值作为AtomicReference的成员,通过cas操作一个对象。

  • 如果一个值原来是A,然后改为B,在改为A。是用cas时检查值没有发生改变,其实已经改变了,这就是ABA问题。

解决方案:

通过追加版本号/时间戳去实现,比如将A->B->A 转换为1A->2B->3A

通过java.util.concurrent.atomic. AtomicStampedReference,该类的compareAndSet方法会先检查引用是否相等,而且还会检查预期标志,如果都相等的情况下,才会更新值。

  • 在循环里一直在尝试修改,如果长时间不成功,那么就会导致CPU开销过大。

解决方案:

让jvm支持多处理器下的pause指令,在一定程度上可以增加CPU的执行效率ß

通过锁,模拟编写一个类似CAS算法的实现【简单版本】

测试代码:

测试用例:模拟实现

输出结果:

测试用例:输出结果

注意,本文内容若有不详之处,请及时联系,学习之路,存疑之路

版权声明:本文为原创文章,未经博主允许不责转载。
地址:https://www.jianshu.com/p/fbab74f4fa74

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容