JMM中的重排序和内存屏障
概述
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序,为了实现某些功能有时会禁止某些重排序,由此引入了内存屏障。
重排序
as-if-serial语义
不管怎么重排序,程序的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义。编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果,但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器做重排序。
重排序种类
- 编译器优化的重排序;
- 指令级并行的重排序;
- 内存系统的重排序;
如何禁止重排序
对于编译器,JMM编译器重排序规则会禁止特定类型的编译器重排序;
对于处理器,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。
内存屏障
概念
是一个CPU的指令
作用
- 确保指令重排序时不会把屏障后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障后面。
- 强制将对缓存的修改操作立即写入主存,利用缓存一致性机制,并且缓存一致性机制会阻止同时修改由两个以上CPU缓存的内存区域数据。
- 如果是写操作,它将导致其他cpu中对应的缓存无效。
通俗地讲,插入一个屏障相当于告诉处理器和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。
内存屏障类型
为了保证可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。
其中StoreLoad指令是现代多处理器都需要使用的,但是它的开销也很昂贵。
volatile插入屏障策略
- 在每个volatile写操作的前面插入一个StoreStore屏障;
- 在每个volatile写操作的后面插入StoreLoad屏障;
- 在每个volatile读操作的后面插入LoadLoad屏障;
- 在每个volatile读操作的后面插入LoadStore屏障;