java内存屏障的原理与应用(jmm)

一. java内存屏障

1.1 什么是内存屏障(Memory Barrier)?
内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。
编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。


1.2 为什么需要内存屏障
我们知道,在多CPU(核)场景下,为了充分利用CPU,会通过流水线将指令并行进行。为了能并行执行,又需要将指令进行重排序以便进行并行执行,那么问题来了,那些指令不是在所有场景下都能进行重排,除了本身的一些规则(如Happens Before 规则)之外,我们还需要确保多CPU的高速缓存中的数据与内存保持一致性, 不能确保内存与CPU缓存数据一致性的指令也不能重排,内存屏障正是通过阻止屏障两边的指令重排序来避免编译器和硬件的不正确优化而提出的一种解决办法
 

1.3 硬件层的内存屏障
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, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。
 

1.4 内存屏障的主要类型
不同硬件实现内存屏障的方式不同,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),以及其他延迟的方式来实现。

二. java内存屏障的使用

2.1 java内存屏障使用介绍

常见的有以下几种:

a. 通过 Synchronized关键字包住的代码区域,当线程进入到该区域读取变量信息时,保证读到的是最新的值.这是因为在同步区内对变量的写入操作,在离开同步区时就将当前线程内的数据刷新到内存中,而对数据的读取也不能从缓存读取,只能从内存中读取,保证了数据的读有效性.这就是插入了StoreStore屏障
b. 使用了volatile修饰变量,则对变量的写操作,会插入StoreLoad屏障.
c. 其余的操作,则需要通过Unsafe这个类来执行.
    UNSAFE.putOrderedObject类似这样的方法,会插入StoreStore内存屏障 
    Unsafe.putVolatiObject 则是插入了StoreLoad屏障
 

2.2 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的适用场景
volatile是在synchronized性能低下的时候提出的。如今synchronized的效率已经大幅提升,所以volatile存在的意义不大。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值