精华内容
下载资源
问答
  • 内存屏障
    2022-04-06 22:53:06





    一、处理器内存屏障



    " 处理器内存屏障 “ 针对 ” CPU " 之间的内存访问乱序CPU 访问外设乱序 问题 ;


    为了 提高 " 流水线 " 性能 , 新式处理器可以采用 " 超标量 体系结构 “” 乱序执行 " 技术 , 可以在 一个时钟周期并行执行多条指令 ;

    但是 CPU 执行优化会导致 指令乱序执行 , 后面的指令先于前面的指令执行 , 导致 寄存器中的值冲突 ;

    CPU 执行优化总结 :

    • 顺序取指令 ,
    • 乱序执行 ,
    • 执行结果顺序提交 ;




    二、Linux 内核处理器内存屏障



    Linux 内核中有 8 8 8 种 " 处理器内存屏障 " ;


    内存屏障 有 4 4 4 种类型 ,

    • ① 通用内存屏障
    • ② 写内存屏障
    • ③ 读内存屏障
    • ④ 数据依赖屏障

    每种类型的 内存屏障 又分为

    • ① 强制性内存屏障
    • ② SMP 内存屏障

    两种类型 ;


    因此将上面 8 8 8 种 " 处理器内存屏障 " 列成表格如下 :

    内存屏障类型强制性内存屏障SMP 内存屏障
    ① 通用内存屏障mb()smp_mb()
    ② 写内存屏障wmb()smp_wmb()
    ③ 读内存屏障rmb()smp_rmb()
    ④ 数据依赖屏障read_barrier_depends()smp_read_barrier_depends()

    如果使用 " 处理器内存屏障 " , 其隐含着同时使用 " 编译器优化屏障 " ; ( 数据依赖屏障 除外 ) ;

    更多相关内容
  • volatile是JVM提供的一种最轻量级的同步机制,因为Java内存模型为volatile定义特殊的访问规则,使其可以实现Java内存模型中的两大特性...这篇文章主要介绍了Java多线程之volatile关键字及内存屏障,需要的朋友可以参考下
  • 内存屏障与java的内存屏障内存屏障前言一、什么是内存屏障?二、volatile变量规则1.volatile简介2.volatile原理3.volatile特性4.volatile变量规则四、内存屏障的标准硬件上面的内存屏障Java的内存屏障五、X86架构的...

    内存屏障

    前言

         在学习JVM的乱序问题的时候,为了现在的CPU效率的提高,会做出各种各样的优化,有个优化就叫做 CPU 乱序执行,CPU乱序执行在单线程环境下是一种很好的优化手段,但是在多线程环境下,就会出现数据不一致的问题,因此就可以通过内存屏障这个机制来处理这个问题。

         而内存屏障是CPU的,与Java的内存屏障有很大的区别,所以接下来就来解释一下内存屏障和Java内存屏障,自己也参考了很多文献,结合自己的理解来深入理解内存屏障和Java内存屏障。

    一、什么是内存屏障?

         其实一开始在接触内存屏障这个词的时候,就可以从内存两个字知道这个是底层与硬件有关的机制,从字面意思屏障指的是屏风或阻挡之物,也有保护遮蔽的含义。所以说内存屏障是硬件之上、操作系统或JVM之下,对并发作出的最后一层支持。

         内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。

         先通过volatile关键字的语义来引出以下几个问题:

    • 可见性
    • 重排序

         为了更好的理解内存屏障,引出内存屏障的一个基本问题:

    • 内存屏障的标准
    • volatile引出的可见性和重排序问题,内存屏障是如何解决的
    • 内存屏障上的几个封装
    • 为了深入理解,了解硬件架构的基本原理

    二、volatile变量规则

    1.volatile简介

         volatile是Java提供的一种轻量级的同步机制。它是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”,可见性的意思是一个线程修改一个共享变量时,另一个线程可以读到这个修改的值,如果volatile使用恰当的话,它比synchronized的使用成本更低,因为它不会引起线程的上下文切换和调度。

    2.volatile原理

         volatile变量修饰的共享变量进行写操作时会在汇编代码前加上lock前缀,lock前缀的指令可以在多核处理器下。

    3.volatile特性

    (1)可见性

         定义:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    线程在JMM(Java memory model)Java内存模型中的流程:

         由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为这个线程创建一个工作内存,工作内存是每个线程的私有数据区域。JMM(Java memory model)中规定所有变量都存储在主内存里,主内存是共享内存区域,所以所有线程都可以进行访问,但是线程对变量的操作(读/写)必须在自己的工作内存中进行。具体步骤:

    • 首先将变量从主内存中拷贝到自己的工作内存空间
    • 然后对变量进行操作
    • 操作完成后再将变量写回主内存

         注意:不能直接操作主内存中的变量,各个线程中的工作内存存储着的是主内存中的变量副本拷贝的。因此不同线程间无法访问对方的工作内存,线程的通信必须通过主内存来完成。

    更加具体步骤,如下图所示:

    image-20220119222602485

         在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

    (2)不保持原子性

         原子性定义:一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

         不保持原子性:对任意单个volatile变量的 读/写 具有原子性,但类似于volatile++这种复合操作不具有原子性。

    (3)禁止指令重排序

         重排序定义:是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。

         重排序并没有严格的定义。整体上可以分为两种:

    • 真·重排序:编译器、底层硬件(CPU等)出于“优化”的目的,按照某种规则将指令重新排序(尽管有时候看起来像乱序)。
    • 伪·重排序:由于缓存同步顺序等问题,看起来指令被重排序了。

         重排序问题源自三种场景:

    • 编译器编译时的优化

    • 处理器执行时的乱序优化

    • 缓存同步顺序(导致可见性问题)

         在多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的共享变量的一致性时无法保证的,所以结果无法预测。volatile 实现了禁止指令重排序优化,从而避免了多线程环境下程序出现乱序执行的现象。

         实现:在对volatile变量进行写操作时,会在写操作后面加入一条**Memory Barriier(内存屏障)**告诉内存和CPU,禁止在内存屏障前后的执行指令重排优化。

    volatile的底层内存屏障:

    image-20220119230200586

    volatile的底层内存屏障:

    image-20220119230603672

    4.volatile变量规则

         volatile变量规则:对volatile变量的写入操作必须在对该变量的读操作之前执行

         把对volatile变量的单个 读/写 看成是使用同一个锁对这些单个 读/写 操作做了同步。从JDK1.5开始,volatile变量的 写-读 可以实现线程间的通信。从内存语意上来讲,volatile的 写-读 与锁的 释放-获取 有相同的内存效果:volatile写和锁的释放有相同的内存语义;volatile读与锁的获取具有相同的内存语义。

    volatile的内存语义

    • volatile写的内存语义:当写一个volatile变量时,JMM(Java内存模型)会把该线程对应的本地内存中的共享变量值刷新到主内存。
    • volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

    volatile内存语义的实现

         为了实现volatile内存语义,JMM会分别限制两种类型的重排序:编译重排序和处理器重排序。

         如下JMM针对编译器制定的volatile重排序规则:

         当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

         当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。

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

         基于保守策略的JMM内存屏障插入策略:为了实现volatile内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

    如:Intel 的内存屏障的设计比较简单,只有三条指令:

    • 指令1 :sfence 写屏障 在sfence 指令前的写操作 必须在sfence 指令的写操作前完成。

    • 指令2: lfence 读屏障 在lfence指令前的读操作 必须在lfence指令的读操作前完成。

    • 指令3:mfence 读写屏障 在mfence 指令的读写操作 必须在mfence指令的读写操作前完成。

    5.volatile使用场景

    四、内存屏障的标准

    硬件上面的内存屏障

    先了解两种命令:

    • Store:将处理器缓存的数据刷新到内存中。
    • Load:将内存存储的数据拷贝到处理器的缓存中。

         Store屏障,是x86的”sfence“指令,在其他指令后插入sfence指令,能让当前线程写入高速缓存中的最新数据更新写入主内存,让其他线程可见。

         Load屏障,是x86上的”ifence“指令,在其他指令前插入ifence指令,可以让高速缓存中的数据失效,强制当前线程从主内存里面加载数据。

    例如 Intel 硬件的内存屏障的设计比较简单,有四种,三条指令。

    • 指令1 :sfence 写屏障 在sfence 指令前的写操作 必须在sfence 指令的写操作前完成。

    • 指令2: lfence 读屏障 在lfence指令前的读操作 必须在lfence指令的读操作前完成。

    • 指令3:mfence 读写屏障 在mfence 指令的读写操作 必须在mfence指令的读写操作前完成。具备ifence和sfence的能力。

    • Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。

    Java的内存屏障

         在java里面有4种,就是 LoadLoad,StoreStore,LoadStore,StoreLoad 实际上也能看出来,这四种都是上面的两种的组合产生的。

    屏障类型指令示例说明
    LoadLoad BarriersLoad1;LoadLoad;Load2该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作
    StoreStore BarriersStore1;StoreStore;Store2该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作
    LoadStore BarriersLoad1;LoadStore;Store2确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作
    StoreLoad BarriersStore1;StoreLoad;Load2该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作。它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令

         StoreLoad Barriers 同时具备其他三个屏障的效果,因此也称之为全能屏障(mfence),是目前大多数处理器所支持的;但是相对其他屏障,该屏障的开销相对昂贵。

         然而,除了mfence,不同的CPU架构对内存屏障的实现方式与实现程度非常不一样。x86架构是在多线程编程中最常见的,下面讨论x86架构中内存屏障的实现。

    五、X86架构的内存屏障

    Intel X86架构的内存屏障的设计比较简单,只有三条指令。

    • Store Barrier 指令1 :sfence指令实现了Store Barrier,相当于StoreStore Barriers。sfence写屏障在sfence指令前的写操作必须在sfence 指令的写操作前完成。

    • **Load Barrier **指令2:lfence指令实现了Load Barrier,相当于LoadLoad Barriers。lfence 读屏障 在lfence指令前的读操作 必须在lfence指令的读操作前完成。

    • Full Barrier 指令3:mfence指令实现了Full Barrier,相当于StoreLoad Barriers。mfence 读写屏障 在mfence 指令的读写操作 必须在mfence指令的读写操作前完成。

    所以说如下详细介绍:

    Store Barrier

         sfence指令实现了Store Barrier,相当于StoreStore Barriers。

         强制所有在sfence指令之前的store指令,都在该sfence指令执行之前被执行,发送缓存失效信号,并把store buffer中的数据刷出到CPU的L1 Cache中;所有在sfence指令之后的store指令,都在该sfence指令执行之后被执行。即,禁止对sfence指令前后store指令的重排序跨越sfence指令,使所有Store Barrier之前发生的内存更新都是可见的

    Load Barrier

         lfence指令实现了Load Barrier,相当于LoadLoad Barriers。

         强制所有在lfence指令之后的load指令,都在该lfence指令执行之后被执行,并且一直等到load buffer被该CPU读完才能执行之后的load指令(发现缓存失效后发起的刷入)。即,禁止对lfence指令前后load指令的重排序跨越lfence指令,配合Store Barrier,使所有Store Barrier之前发生的内存更新,对Load Barrier之后的load操作都是可见的

    Full Barrier

         mfence指令实现了Full Barrier,相当于StoreLoad Barriers。

         mfence指令综合了sfence指令与lfence指令的作用,强制所有在mfence指令之前的store/load指令,都在该mfence指令执行之前被执行;所有在mfence指令之后的store/load指令,都在该mfence指令执行之后被执行。即,禁止对mfence指令前后store/load指令的重排序跨越mfence指令,使所有Full Barrier之前发生的操作,对所有Full Barrier之后的操作都是可见的。

    六、volatile引出的可见性和重排序问题,内存屏障是如何解决的

    以x86架构为例,JVM对volatile变量的处理如下:

    • 在写volatile变量v之后,插入一个sfence。这样,sfence之前的所有store(包括写v)不会被重排序到sfence之后,sfence之后的所有store不会被重排序到sfence之前,禁用跨sfence的store重排序;且sfence之前修改的值都会被写回缓存,并标记其他CPU中的缓存失效。
    • 在读volatile变量v之前,插入一个lfence。这样,lfence之后的load(包括读v)不会被重排序到lfence之前,lfence之前的load不会被重排序到lfence之后,禁用跨lfence的load重排序;且lfence之后,会首先刷新无效缓存,从而得到最新的修改值,与sfence配合保证内存可见性。

    在另外一些平台上,JVM使用mfence代替sfence与lfence,实现更强的语义。

    二者结合,共同实现了Happens-Before关系中的volatile变量规则。

    八、CAS

         在x86架构上,CAS被翻译为"lock cmpxchg..."。cmpxchg是CAS的汇编指令。在CPU架构中依靠lock信号保证可见性并禁止重排序。

         lock前缀是一个特殊的信号,执行过程如下:

    • 对总线和缓存上锁。
    • 强制所有lock信号之前的指令,都在此之前被执行,并同步相关缓存。
    • 执行lock后的指令(如cmpxchg)。
    • 释放对总线和缓存上的锁。
    • 强制所有lock信号之后的指令,都在此之后被执行,并同步相关缓存。

         因此,lock信号虽然不是内存屏障,但具有mfence的语义(当然,还有排他性的语义)。与内存屏障相比,lock信号要额外对总线和缓存上锁,成本更高。

    九、锁

    ​     JVM的内置锁通过操作系统的管程实现。且不论管程的实现原理,由于管程是一种互斥资源,修改互斥资源至少需要一个CAS操作。因此,锁必然也使用了lock信号,具有mfence的语义。

         锁的mfence语义实现了Happens-Before关系中的监视器锁规则。

    CAS具有同样的mfence语义,也必然具有与锁相同的偏序关系。尽管JVM没有对此作出显式的要求。

    参考文献:一文解决内存屏障

    展开全文
  • CPU 内存屏障 定义 读写屏障指令 为什么会出现内存屏障 java内存屏障 java内存屏障存在意义 java中内存屏障的主要类型 LoadLoad 屏障 StoreStore 屏障 LoadStore 屏障 StoreLoad 屏障 Synchronized 锁 volatile语义...

    CPU 内存屏障

    定义

    内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。

    读写屏障指令

    指令1 :sfence 写屏障 在sfence 指令前的写操作 必须在sfence 指令的写操作前完成。
    指令2: lfence 读屏障 在lfence指令前的读操作 必须在lfence指令的读操作前完成。
    指令3:mfence 读写屏障 在mfence 指令的读写操作 必须在mfence指令的读写操作前完成。

    为什么会出现内存屏障

    内存屏障存在的意义就是为了解决程序在运行过程中出现的内存乱序访问问题,内存乱序访问行为出现的理由是为了提高程序运行时的性能。

    java内存屏障

    java内存屏障存在意义

    每个CPU都会有自己的缓存,缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。
    在Java中通过用volatile关键字修饰变量,Synchronized关键字包住的代码区域的方法,来解决上述问题,通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。

    java中内存屏障的主要类型

    不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。
    Java内存屏障主要有Load和Store两类。
    对Load Barrier来说,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
    对Store Barrier来说,在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存

    对于Load和Store,在实际使用中,又分为以下四种:

    LoadLoad 屏障

    序列:Load1,Loadload,Load2
    确保Load1所要读入的数据能够在被Load2和后续的load指令访问前读入。通常能执行预加载指令或/和支持乱序处理的处理器中需要显式声明Loadload屏障,因为在这些处理器中正在等待的加载指令能够绕过正在等待存储的指令。 而对于总是能保证处理顺序的处理器上,设置该屏障相当于无操作。

    StoreStore 屏障

    序列:Store1,StoreStore,Store2
    确保Store1的数据在Store2以及后续Store指令操作相关数据之前对其它处理器可见(例如向主存刷新数据)。通常情况下,如果处理器不能保证从写缓冲或/和缓存向其它处理器和主存中按顺序刷新数据,那么它需要使用StoreStore屏障。

    LoadStore 屏障

    序列: Load1; LoadStore; Store2
    确保Load1的数据在Store2和后续Store指令被刷新之前读取。在等待Store指令可以越过loads指令的乱序处理器上需要使用LoadStore屏障。

    StoreLoad 屏障

    序列: Store1; StoreLoad; Load2
    确保Store1的数据在被Load2和后续的Load指令读取之前对其他处理器可见。StoreLoad屏障可以防止一个后续的load指令 不正确的使用了Store1的数据,而不是另一个处理器在相同内存位置写入一个新数据。正因为如此,所以在下面所讨论的处理器为了在屏障前读取同样内存位置存过的数据,必须使用一个StoreLoad屏障将存储指令和后续的加载指令分开。Storeload屏障在几乎所有的现代多处理器中都需要使用,但通常它的开销也是最昂贵的。它们昂贵的部分原因是它们必须关闭通常的略过缓存直接从写缓冲区读取数据的机制。这可能通过让一个缓冲区进行充分刷新(flush),以及其他延迟的方式来实现。

    Synchronized 锁

    通过 Synchronized关键字包住的代码区域,当线程进入到该区域读取变量信息时,保证读到的是最新的值.这是因为在同步区内对变量的写入操作,在离开同步区时就将当前线程内的数据刷新到内存中,而对数据的读取也不能从缓存读取,只能从内存中读取,保证了数据的读有效性.这就是插入了StoreStore屏障

    volatile语义中的内存屏障

    volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

    在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
    在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

    由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。
    volatile与synchronized对比
    volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度

    final语义中的内存屏障

    对于final域,编译器和CPU会遵循两个排序规则:

    新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序;
    初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序;(晦涩,意思就是先赋值引用,再调用final值)

    总之上面规则的意思可以这样理解,必需保证一个对象的所有final域被写入完毕后才能引用和读取。这也是内存屏障的起的作用:

    写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。
    读final域:在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。

    X86处理器中,由于CPU不会对写-写操作进行重排序,所以StoreStore屏障会被省略;而X86也不会对逻辑上有先后依赖关系的操作进行重排序,所以LoadLoad也会变省略。

    本文参考文献
    内存屏障
    java内存屏障的原理与应用

    展开全文
  • 内存屏障原理解析

    2019-04-08 19:12:34
    内存屏障分析,内存屏障分析,内存屏障分析,内存屏障分析,内存屏障分析
  • 内存屏障

    2021-02-28 17:22:57
    什么是内存屏障(Memory Barrier)? 内存屏障(memory barrier)是一个CPU指令。 内存屏障有两个作用: 阻止屏障两侧的指令重排序; 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。 ...

    什么是内存屏障(Memory Barrier)?
    内存屏障(memory barrier)是一个CPU指令。

    • 内存屏障有两个作用:
    1. 阻止屏障两侧的指令重排序;
    2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

    编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。

    硬件层的内存屏障
    Intel硬件提供了一系列的内存屏障,主要有:

    1. lfence,是一种Load Barrier 读屏障
    2. sfence, 是一种Store Barrier 写屏障
    3. mfence, 是一种全能型的屏障,具备ifence和sfence的能力
    4. Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, 等指令。

    内存屏障的主要类型
    不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。
    Java内存屏障主要有Load和Store两类。
    对Load Barrier来说,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
    对Store Barrier来说,在写指令之后插入写屏障,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

    对于Load和Store,在实际使用中,又分为以下四种:

    LoadLoad屏障
    对于Load1; LoadLoad; Load2 ,操作系统保证在Load2及后续的读操作读取之前,Load1已经读取。
    StoreStore屏障
    对于Store1; StoreStore; Store2 ,操作系统保证在Store2及后续的写操作写入之前,Store1已经写入。
    LoadStore屏障
    对于Load1; LoadStore; Store2,操作系统保证在Store2及后续写入操作执行前,Load1已经读取。
    StoreLoad屏障
    对于Store1; StoreLoad; Load2 ,操作系统保证在Load2及后续读取操作执行前,Store1已经写入,开销较大,但是同时具备其他三种屏障的效果。

    在这里插入图片描述

    volatile实现原理

    Volatile基本介绍
    Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。
    Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
    volatile作用
    能保证可见性和防止指令重排序

    volatile与synchronized对比
    volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度

    volatile如何保证可见性、防止指令重排序
    volatile保持内存可见性和防止指令重排序的原理,本质上是同一个问题,也都依靠内存屏障得到解决
    在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。
    Java代码: instance = new Singleton();//instance是volatile变量
    汇编代码: 0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);
    lock前缀指令相当于一个内存屏障(也称内存栅栏),内存屏障主要提供3个功能:
    1、 确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
    2、 强制将对缓存的修改操作立即写入主存,利用缓存一致性机制,并且缓存一致性机制会阻止同时修改由两个以上CPU缓存的内存区域数据;
    3、如果是写操作,它会导致其他CPU中对应的缓存行无效。
    一个处理器的缓存回写到内存会导致其他处理器的缓存失效。
    处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。例如CPU A嗅探到CPU B打算写内存地址,且这个地址处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,
    在下次访问相同内存地址时,强制执行缓存行填充。
    volatile关键字通过“内存屏障”来防止指令被重排序。
    为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。然而,对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。
    下面是基于保守策略的JMM内存屏障插入策略:
    在每个volatile写操作的前面插入一个StoreStore屏障。
    在每个volatile写操作的后面插入一个StoreLoad屏障。
    在每个volatile读操作的后面插入一个LoadLoad屏障。
    在每个volatile读操作的后面插入一个LoadStore屏障。
    volatile为什么不能保证原子性
    原子操作是一些列的操作要么全做,要么全不做,而volatile 是一种弱的同步机制,只能确保共享变量的更新操作及时被其他线程看到,以最常用的i++来说吧,包含3个步骤
    1,从内存读取i当前的值 2,加1 变成 3,把修改后的值刷新到内存,volatile无法保证这三个不被打断的执行完毕,如果在刷新到内存之前有中断,此时被其他线程修改了,之前的值就无效了
    volatile的适用场景
    ile无法保证这三个不被打断的执行完毕,如果在刷新到内存之前有中断,此时被其他线程修改了,之前的值就无效了
    volatile的适用场景
    volatile是在synchronized性能低下的时候提出的。如今synchronized的效率已经大幅提升,所以volatile存在的意义不大。

    展开全文
  • 什么是内存屏障内存屏障是一条指令,该指令可以对编译器(软件)和处理器(硬件)的指令重排做出一定的限制,比如,一条内存屏障指令可以禁止编译器和处理器将其后面的指令移到内存屏障指令之前。 为什么需要内存...
  • Linux-内存屏障

    2021-05-10 18:54:59
    一种可能的解决方案是,每个CPU都有自己唯一可访问内存,处理器间通过消息传递进行通信。这种架构的问题是带给程序员(尤其是系统程序员)巨大的编程负担,因为需要处理数据分隔与传递。相反,被广泛应用的另一种架构...
  • - Java多线程程序通常使用高层程序设计语言中的同步原语,比如volatile和synchronized,因此一般不需要明确使用内存屏障。 - 也就是说在Java中我们使用的是volatile和synchronized关键字,javac编译转化成字节码的...
  • ARM64中的内存屏障指令

    千次阅读 2022-05-30 07:58:45
    本章重点介绍内存屏障指令产生的原因、ARM64处理器内存屏障指令以及内存屏障的案例分析等内容。 18.2.1 使用内存屏障的场景 在大部分场景下,我们不用特意关注内存屏障的,特别是在单处理器系统里,虽然CPU内部...
  • 一.CPU内存乱序访问发生的原因 (1)编译优化,指令重排导致乱序 由于编译器在编译代码时不感知多线程并发执行情况。所以,编译器对代码的...内存屏障的作用 允许软件开发者使用硬件提供的特殊指令控制编译器和C...
  • 源码级分析:86平台上,hotspot内存屏障的实现和缓存一致性协议(例如MESI)是什么关系?
  • 内存屏障(转)

    2021-05-11 17:38:51
    什么是内存屏障(Memory Barriers) memory barriers 内存屏障是一种底层原语,在不同计算机架构下有不同的实现细节。本文主要在x86_64处理器下,通过Linux及其内核代码来分析和使用内存屏障 对大多数应用层开发者来...
  • JAVA内存屏障

    2022-01-11 20:21:27
    文章目录指令重排序什么是内存屏障synchronized和volatilevolatile保证原子性吗? 指令重排序 程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由...
  • 内存屏障(英语:Memory barrier),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,它使得 CPU 或编译器在对内存进行操作的时候, 严格按照一定的顺序来执行, 也就是说在内存屏障之前的指令和内存屏障...
  • Java内存屏障

    2022-01-11 23:10:15
    java内存屏障 1.1 什么是内存屏障(Memory Barrier)? 内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的...
  • 内存屏障机制

    2015-10-08 11:43:49
    Linux内存屏障知识讲解,彻底了解内存屏障
  • 什么是内存屏障内存屏障是一条指令,该指令可以对编译器(软件)和处理器(硬件)的指令重排做出一定的限制,比如,一条内存屏障指令可以禁止编译器和处理器将其后面的指令移到内存屏障指令之前。 为什么需要...
  • volatile 和 内存屏障

    2021-03-16 14:34:03
    接下来看看volatile是如何解决上面两个问题的:被volatile修饰的变量在编译成字节码文件时会多个lock指令,该指令在执行过程中会生成相应的内存屏障,以此来解决可见性跟重排序的问题。内存屏障的作用:1.在有内存...
  • 线程cosume中使用 memory_order_acquire内存屏障,保证了consumer线程后面的所有读写(包括非原子)操作不能重排序到acquire之前,并且consumer的load操作一旦完成,线程producer release之前的读写操作对producer...
  • 首先参考欢神的理解,其实内存屏障很类似于CPU给用户提供的接口,因为其作用就是在保证缓存一致性效率的同时防止store buffer和Invaildate queue异步执行带来的一致性问题,这个问题在用户的角度来说通常
  • linux 内存屏障

    2020-10-18 21:09:45
    目录 ...内存屏障(Memory barrier) 目的 分类 编译器屏障 Compiler Barrior CPU屏障(CPU memory Barrior) 前言 大家经常说的一个词汇叫做所见即所得,有些编程工具是所见即所得的,给程序
  • 内存屏障详解

    千次阅读 2021-03-08 09:30:17
    1、什么是内存屏障? 你可以先这么理解,在拥有多核CPU的ARM处理器中,会存在一种现象:那就是各个CPU在进行数据共享的时候(数据共享的目的是为了保证数据一致性),某种情况下CPU可能是处于阻塞的状态,也就是CPU...
  • 这需要用到内存屏障 ## 内存屏障(memory barriers) 内存屏障,也叫内存栅栏(Memory Fence)。分编译器屏障(Compiler Barrier,也叫优化屏障) 和CPU内存屏障,其中编译器屏障只对编译器有效。我们一般提的内存...
  • 指令重排 2.as-if-serial语义 五、volatile与内存屏障(Memory Barrier) 1.内存屏障(Memory Barrier) 2.volatile的内存语义实现 六、JMM对volatile的特殊规则定义 一、volatile关键字介绍及底层原理 1.volatile的...
  • 原子操作(atomic)是无锁编程(Lock-Free Programming)的基础。以往,要使用atomic操作,我们一般会使用gcc内置的原子操作接口...我尝试通过本文对C++11中内存屏障(内存顺序)的一些基本概念和使用情况进行总结。C++内...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,286
精华内容 17,314
关键字:

内存屏障

友情链接: Atris.03.2016.Fix.zip