zoukankan      html  css  js  c++  java
  • Java并发之同步原语

    volatile:

    定义:Java编程语言允许线程访问共享变量,为了确保共享变量内被准确和一致性地更新,线程应该确保通过排它锁单独获得这个变量。根据volatile的定义,volatile有锁的语义。

    作用:1.保证共享变量的可见性(这是volatile作为轻量级锁的基础);

        这里可见性的意思是:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值(与上篇定义的可见性有点区别啊,这里与上篇相比没有强调因重排序带来的有序性问题,进而导致的操作间可见性问题,也就是后面操作本来应该能看到前面操作结果的,结果因为重排序而看不到了,导致出现不可见性问题,笔者认为这本质上还是属于重排序问题。所以上篇定义的可见性,最后一句存在错误,但为了强调可见性与重排序(有序性)之间的区别,笔者并没有修改,当然也保留了这部分的解释)。

       2.禁止重排序(根据happens-before规则)

    实现原理:

      1.保证共享变量可见性的实现原理(以X86处理器来分析):

      instance = new Single();//instance是volatile变量

      转换成汇编代码,如下:

      0x01a3deld: movb $0x0,0x1104800(%esi);0x01aa3de24:lock addl $0x0,(%esp);

      有volatile修饰的共享变量进行写操作的时候会多出第二行(带lock前缀的)汇编代码。

      Lock前缀的指令在多核处理器下的作用:

      1.将当前处理器的缓冲行的数据写会到系统内存;

      Lock前缀指令导致在执行指令期间,声言处理器的LOCK#信号。在多处理环境中,LOCL#信号确保会在声言该信号期间,处理器可以独占任何共享内存。(采取机制有锁总线,锁缓存,目前的处理大多选择后者,因为锁总线的开销太大)。  

      对于目前的处理器,如果访问的区域已经缓存在处理器内部,则不会声言LOCK#信号,处理器会锁定这块内存的缓存并刷新到内存,并使用缓存一致性机制来确保修改的原子性,此操作称为“缓存锁定”,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。同时,Lock前缀指令会引起处理器缓存刷新到内存。

      2.这个写内存的操作会使在其它CPU里缓存了该内存地址的数据无效。

      实现机制:一个处理器的缓存刷新到内存会导致其它处理器的缓存无效。处理器使用嗅探技术来保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。

      缓存一致性协议:在多处理下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址(中的数据)被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行操作的时候,会重新从系统内存把数据读到处理器缓存里(看到这里是不是明白了只对volatile变量的写操作进行了处理(增加lock前缀),因为处理器缓存里的数据有么有效,有效就是最新值,要么无效,需要到主内存去读取)。

    现在总结一下LOCK前缀指令的作用:

    1.确保处理器修改共享数据的原子性(此时修改的值还在缓存),通过锁总线,锁缓存实现的

    2.将修改后的值刷新到内存,同时刷新内存的这个动态,会被其他处理器嗅探到,然后其他处理器会将他们缓存里的数据置为无效状态。

    2,volatile禁止重排序的实现原理

    在讲解实现原理之前,我们先说一下volatile的内存语义:

    内存语义的实现也需要可见性作为基础的。内存屏障只是禁止重排序的基础。

    volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值(所有的共享变量)刷新到主内存。

    volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效(本地内存中的所有数据都无效了),线程接下来将从主内存中读取数据。

    volatile内存语义的实现:

    volatitle重排序规则表:

    从上表可知:

    1、当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。

    2、当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。

    3、当第一个操作是volatile写时,第二个volatile操作是volatile读时,不能重排序。

     通过volatile重排序的规则,编译器编译时会在这三种情况下插入相应的内存屏障,来保证volatile的内存语义。插入内存屏障的规则:

    1、在每volatile写,之前插入StoreStore内存屏障指令,之后插入StoreLoad屏障;

    2、在每volatile读,之后插入内存LoadLoad,LoadStore屏障指令。

    这些内存屏障插入措施是非常保守的。因为有些内存屏障可以根据内存访问的具体情况(比如只有一个volatile读时,所有的屏障都可以省略)是可以省略掉的。尤其是这些措施是独立于处理器平台之上的,不同的处理器重排序的规则都不一样。比如,X86处理器,只允许写/读重排序,这种情况下,上面的内存屏障插入规则只需要StoreLoad就可以了。

    根据volatile禁止重排序的规则,以及内存屏障指令的作用,我们可以总结volatile的一个特性:

    对volatile变量的单个读/写,可以看成是使用同一个锁对这些单个volatile读/写做了同步,正是如此,volatile被称为轻量级锁。在功能上,锁比volatile更强大;在可伸缩性和执行性能上,volatile更有优势。

    不同的处理器进行重排序的规则也不相同,以X86处理器为例,它允许的重排序就只有写-读。

    synchronized

    final

  • 相关阅读:
    php7垃圾回收机制
    PHP-FPM
    go main.main之前的准备
    ElementUI MessageBox 弹框
    ElementUI table 样式修改
    ElementUI Dropdown 下拉菜单
    vue+django实现下载文件
    ElementUI 点击展开/隐藏
    vue+django实现websocket连接
    ElementUI 上传文件以及限制
  • 原文地址:https://www.cnblogs.com/gulingjingguai/p/8134311.html
Copyright © 2011-2022 走看看