美文网首页
[Java] volatile 实现

[Java] volatile 实现

作者: 不一样的卡梅利多 | 来源:发表于2019-08-24 17:50 被阅读0次
    一、cpu 结构以及原子指令简介

    1、处理器结构发展历史

    单核心
    1、core —> 内存

    2、多核心
    core1 —> 内存
    core2 —> 内存

    3、 多核+缓存 (内存和cpu 频率差别很大)
    core1 —> cache1(1-2) ->cahce3 —>内存
    core2 —> cahce2(1-2)->cache3—> 内存

    4、多核+缓存+缓冲区
    core1 —>[ buffue1] —> cacheN —>[invalidQueue1]—>内存
    core2 —>[buffue2] —> cacheN —>[invalidQueue 1]—>内存

    2、处理理原子操作/同步支持

    原子操作定义:
    同一时间只能一个cpu 访问同一区域数据,并且该指令执行过程中不能被中断,不能上下文切换。一般用锁来实现

    锁实现:
    对同一个标识位(内存/寄存器)更新,用1更新标志位,如果标志位为1 ,则不能更新,表示已加锁,返回0. 反之成功返回1,如果锁操作同时进行,那么将会排序。也会串行执行指令。

    3、intel 原子操作实现历史(与cpu 结构设计相关)

    0、基础操作类型(读写)
    1、读或写一个字节
    2、读或写一个在16位边界对齐的字
    3、读或写一个在32位边界对齐的双字

    1、可靠的原子操作(无缓存,单核),如下操作是原子的
    基础操作类型cpu 默认为原子操作

    2、总线加锁,使用LOCK#信号和LOCK指令前缀(多核cpu,有缓存或者无缓存)
    1、如果有缓存,并且实现了缓存一致性协议
    Lock指令将无效, 自动由缓存一致性协议来保证 基础操作类型cpu 默认为原子操作,
    缓存一致性协议还保证,共享的缓存数据只能由一个cpu 更新。
    2、没有实现缓存一致性协议或者无缓存结构
    LOCK 指令将在cpu 控制总线上面发出一个lock 信号,只有一个cpu 能够访问共享数据,并且完成原子操作。(如果多个cpu 同时发出,总线仲裁设备也只会让一个cpu 能够申请成功)

    3、内存屏障指令LFENCE,SFECE,MFENCE(多核cpu,除缓存结构外,还有buffer 结构)
    0、为什么增加了buffer ,invalidQueue等结构。 为了让cpu 更快的执行。写数据先回buffer ,不写cache 这样 写变成了临时写(类似于异步编程),然后就可以继续执行后续的读操作(这样cpu 就会乱序执行指令)。
    1、为了保证cpu 执行的顺序性,提供了内存屏障指令。并且规定: 读操作不能越过LFENCE和MFENCE指令,读写操作都不能跨越I/O指令,加锁指令,写操作不能越过SFECE和MFENCE指令

    4、lock 指令与内存屏障与缓存一致性协议的关系 ?

    1、 lock 只能保证单条指令的原子操作。lock 保证只有同时只有一个cpu 写,并且写完成后,保证写的变量在所有cpu里面数据一致
    2、 如果有缓存一致性协议的情况下,lock 指令无效。有缓存一致性协议保证。
    3、缓存一致性协议,能报保证数据写入到cache 后,各个cpu 里面的数据一致,但是如果数据写入的是buffer,
    那么此时缓存一致性协议也将无效。需要通过内存屏障指令。让buffer 里面数据全部写入缓存后。才能执行
    后续的指令。buffer flush 到cache 后。就能够保证所有cache 数据一致。
    4、内存屏障指令可以保证一部分指令在另一部分指令之前先完成操作。有可能部分指令见是乱序的。部分与部分之间是有序的。
    数据同步的粒度大于lock 指令
    5、lock 指令与内存屏障指令是cpu 提供给程序自定义使用的指令。让程序有更大的灵活性,充分利用cpu资源(lock 与内存屏障同步的粒度不一样)

    二、java volitale 实现

    1、 java 语言规范定义 :

    java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量

    2、volitale 实现目标 :

    需要兼容多种不同cpu 结构,不同操作系统。volitale 关键字都能够达到java 语言规范定义的效果

    3、volitale 实现细节

    1、lock 指令的使用, JIT 编译 会判断当前的cpu 体系结构,来选择是否添加lock 指令。(因为如果cpu 有缓存一致性协议,那么将由缓存一致性协议来保证原子操作以及数据一致)
    2、内存屏障指令的使用。
    1、为什么要 加强了volitale 的语义?
    由于volitale 变量可能读取普通变量的值。需要保证普通变量的加载必须要在volitale 读之前先执行。所以需要内存屏障。写同理
    2、内存屏障粒度,由于处理器对内存顺序实现不一样。volitale 也需要动态选择使用哪一种屏障指令。比如 :
    写读:写指令必须先与后续读完成
    读读:前部分读必须先于后部分读完成
    读写:前部分读指令必须先于后部分写完成
    写写:前部分写必须先于后部分写
    3、编译器必须保证编译的时候不能乱序。如果编译的时候乱序了,那么执行的顺序将是错误的。编译器还必须实现针对不同的处理器结构生成正确的内存屏障指令。

    三、参考资料

    1、《java 并发编程艺术》
    2、《深入理解并行编程》
    3、《计算机组成与设计/硬件软件接口》
    4、 英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A

    相关文章

      网友评论

          本文标题:[Java] volatile 实现

          本文链接:https://www.haomeiwen.com/subject/vkmqectx.html