我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励欢迎关注微信公众号「架构染色」交流和学习
一、了解重排序
为了性能,编译时和运行时都会有重排序,造成指令执行顺序变了,宏观上从这3点了解重排序:
- 线程内有序:如果再本线程内观察,所有的操作都是有序的,即线程内表现为串行的语义(Within Thread As-If-Serial Semantics)。
- 线程间无序:如果再一个线程中观察另一个线程,所有的操作都是无序的,即 指指令重排序现象和工作内存与主内存同步延迟现象。
- 总会有重排序:指令重排序在任何时候都有可能发生,与是否为多线程无关,之所以在单线程下感觉没有发生重排序,是因为线程内表现为串行的语义的存在。
二、重排序遵守 as-if-serial语义
as-if-serial语义 : 不管怎么重排序(编译器和处理器为了提高并行度),程序在单线程中的执行结果不会改变。
编译器、runtime和处理器都必须遵守as-if-serial语义:
- 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。
- 如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序
正确的理解as-if-serial语义的功效:
- 如果在本线程内观察,所有的操作都是有序的;
- 如果在一个线程中观察另一个线程,所有的操作都是无序的。
三、重排序的类型
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。属于编译期重排序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。属于运行期重排序。
- 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行,属于运行期重排序。
从编译运行视角分为两类:
- 编译期重排序:包括 编译器优化的重排序。
- 运行期重排序:包括 指令级并行的重排序,内存系统的重排序。
四、为何要禁止重排序
从修复Java内存模型,第2部分里的一段话开始思考
理解JMM所需的关键概念之一是可见性 -您如何知道如果线程A执行someVariable = 3,其他线程将看到线程A在其中 写入的值3?存在许多原因,为什么另一个线程可能不会立即为以下值看到值3 someVariable:可能是因为编译器已对指令进行了重新排序以便更有效地执行,或者已将someVariable其缓存在寄存器中,或者将其值写入了缓存在写处理器上但尚未刷新到主内存,或者在读处理器的缓存中有旧的(或陈旧的)值。内存模型确定线程何时可以可靠地“看到”对其他线程所做的变量的写入。特别是,内存模型为定义了语义volatile,synchronized,final这确保了跨线程的内存操作可见性。
从其原文中不难看出,导致可见性的原因有很多
- 为了提升性能而实施的编译期重排序。
- 数据在寄存器中。
- cpu缓存的更改未同步到主内存中 或 内存中的更改未同步到cpu缓存(运行期重排序)。
- 。。。
其中两次提到重排序会导致可见性问题。那么某些情况下为了保证可见性则需禁止重排序
五、最后说一句
我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。